Skip to content

Commit

Permalink
Small content-length values caching
Browse files Browse the repository at this point in the history
  • Loading branch information
franz1981 authored and vietj committed Nov 18, 2024
1 parent 4c38e2d commit 14e9377
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,7 @@ private void prepareHeaders(long contentLength) {
} else {
// Set content-length header automatically
if (contentLength >= 0 && !headers.contains(HttpHeaders.CONTENT_LENGTH) && !headers.contains(HttpHeaders.TRANSFER_ENCODING)) {
String value = contentLength == 0 ? "0" : String.valueOf(contentLength);
headers.set(HttpHeaders.CONTENT_LENGTH, value);
headers.set(HttpHeaders.CONTENT_LENGTH, HttpUtils.positiveLongToString(contentLength));
}
}
if (headersEndHandler != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ Future<Void> write(ByteBuf chunk, boolean end) {
chunk = Unpooled.EMPTY_BUFFER;
}
if (end && !headWritten && needsContentLengthHeader()) {
headers().set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(chunk.readableBytes()));
headers().set(HttpHeaderNames.CONTENT_LENGTH, HttpUtils.positiveLongToString(chunk.readableBytes()));
}
boolean sent = checkSendHeaders(end && !hasBody && trailers == null, !hasBody);
if (hasBody || (!sent && end)) {
Expand Down Expand Up @@ -545,7 +545,7 @@ public Future<Void> sendFile(String filename, long offset, long length) {
long contentLength = Math.min(length, fileLength);
// fail early before status code/headers are written to the response
if (headers.get(HttpHeaderNames.CONTENT_LENGTH) == null) {
putHeader(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(contentLength));
putHeader(HttpHeaderNames.CONTENT_LENGTH, HttpUtils.positiveLongToString(contentLength));
}
if (headers.get(HttpHeaderNames.CONTENT_TYPE) == null) {
String contentType = MimeMapping.mimeTypeForFilename(filename);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ private boolean requiresContentLength() {
private Future<Void> write(ByteBuf buff, boolean end) {
if (end) {
if (buff != null && requiresContentLength()) {
headers().set(CONTENT_LENGTH, String.valueOf(buff.readableBytes()));
headers().set(CONTENT_LENGTH, HttpUtils.positiveLongToString(buff.readableBytes()));
}
} else if (requiresContentLength()) {
throw new IllegalStateException("You must set the Content-Length header to be the total size of the message "
Expand Down
24 changes: 24 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/impl/HttpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -837,4 +837,28 @@ public static HostAndPort socketAddressToHostAndPort(SocketAddress socketAddress
}
return null;
}

private static final String[] SMALL_POSITIVE_LONGS = new String[256];

/**
* This try hard to cache the first 256 positive longs as strings [0, 255] to avoid the cost of creating a new
* string for each of them.<br>
* The size/capacity of the cache is subject to change but this method is expected to be used for hot and frequent code paths.
*/
public static String positiveLongToString(long value) {
if (value < 0) {
throw new IllegalArgumentException("contentLength must be >= 0");
}
if (value >= SMALL_POSITIVE_LONGS.length) {
return Long.toString(value);
}
final int index = (int) value;
String str = SMALL_POSITIVE_LONGS[index];
if (str == null) {
// it's ok to be racy here, String is immutable hence it benefits from safe publication!
str = Long.toString(value);
SMALL_POSITIVE_LONGS[index] = str;
}
return str;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2011-2024 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/

package io.vertx.benchmarks;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.util.Random;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.BenchmarkParams;
import org.openjdk.jmh.infra.Blackhole;

import io.vertx.core.http.impl.HttpUtils;

@Warmup(iterations = 10, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 10, time = 200, timeUnit = MILLISECONDS)
@Fork(value = 2)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(java.util.concurrent.TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class ContentLengthToString {

@Param({"255", "511", "1023" })
public int maxContentLength;
private int[] contentLengthIndexes;
private long inputSequence;

@Setup
public void init(Blackhole bh, BenchmarkParams params) {
final int MAX_CONTENT_LENGTH_SIZE = 1024;
if (maxContentLength >= MAX_CONTENT_LENGTH_SIZE) {
throw new IllegalArgumentException("maxContentLength must be < " + MAX_CONTENT_LENGTH_SIZE);
}
contentLengthIndexes = new int[128 * MAX_CONTENT_LENGTH_SIZE];
if (Integer.bitCount(contentLengthIndexes.length) != 1) {
throw new IllegalArgumentException("contentLengthIndexes must be a power of 2");
}
Random rnd = new Random(42);
for (int i = 0; i < contentLengthIndexes.length; i++) {
contentLengthIndexes[i] = rnd.nextInt(maxContentLength);
}
}

private long nextContentLength() {
int[] contentLengthIndexes = this.contentLengthIndexes;
int nextInputIndex = (int) inputSequence & (contentLengthIndexes.length - 1);
int contentLength = contentLengthIndexes[nextInputIndex];
inputSequence++;
return contentLength;
}

@Benchmark
public String contentLengthToString() {
return String.valueOf(nextContentLength());
}

@Benchmark
public String contentLengthHttpUtils() {
return HttpUtils.positiveLongToString(nextContentLength());
}

}

0 comments on commit 14e9377

Please sign in to comment.