/* * Copyright 2019 LINE Corporation * * LINE Corporation licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.linecorp.armeria.common; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Map.Entry; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.infra.Blackhole; import com.google.common.base.Strings; import com.google.common.escape.Escaper; import com.google.common.net.UrlEscapers; import com.linecorp.armeria.internal.common.util.TemporaryThreadLocals; import io.netty.handler.codec.http.QueryStringEncoder; /** * Microbenchmarks for encoding query parameters. */ public class QueryStringEncoderBenchmark { private static final Escaper guavaEscaper = UrlEscapers.urlFormParameterEscaper(); static final QueryParams ASCII_PARAMS; static final QueryParams UNICODE_PARAMS; static final QueryParams MIXED_PARAMS; static final QueryParams LONG_PARAMS; static { final QueryParamsBuilder ascii = QueryParams.builder(); for (int i = 0; i < 10; i++) { ascii.add("alpha" + i, "beta_gamma") .add("delta" + i, "epsilon_zeta") .add("eta" + i, "theta_iota") .add("kappa" + i, "lambda_mu"); } ASCII_PARAMS = ascii.build(); final QueryParamsBuilder unicode = QueryParams.builder(); for (int i = 0; i < 10; i++) { unicode.add("알파" + i, "베타・감마") // Hangul .add("アルファ" + i, "ベータ・ガンマ") // Katakana .add("电买车红" + i, "无东马风") // Simplified Chinese .add("🎄❤️😂" + i, "🎅🔥😊🎁"); // Emoji } UNICODE_PARAMS = unicode.build(); final QueryParamsBuilder mixed = QueryParams.builder(); for (int i = 0; i < 10; i++) { mixed.add("foo" + i, "alpha・ベータ") .add("bar" + i, "ガンマ・delta") .add("baz" + i, "nothing_无_east_东_horse_马_wind_风") .add("qux" + i, "santa_🎅_fire_🔥_smile_😊_present_🎁"); } MIXED_PARAMS = mixed.build(); final QueryParamsBuilder looong = QueryParams.builder(); for (Map.Entry<String, String> e : MIXED_PARAMS) { looong.add(e.getKey(), Strings.repeat(e.getValue(), 10)); } LONG_PARAMS = looong.build(); } @Benchmark public void armeriaAscii(Blackhole bh) { bh.consume(ASCII_PARAMS.toQueryString()); } @Benchmark public void armeriaUnicode(Blackhole bh) { bh.consume(UNICODE_PARAMS.toQueryString()); } @Benchmark public void armeriaMixed(Blackhole bh) { bh.consume(MIXED_PARAMS.toQueryString()); } @Benchmark public void armeriaLong(Blackhole bh) { bh.consume(LONG_PARAMS.toQueryString()); } @Benchmark public void guavaAscii(Blackhole bh) { bh.consume(guavaEncode(ASCII_PARAMS)); } @Benchmark public void guavaUnicode(Blackhole bh) { bh.consume(guavaEncode(UNICODE_PARAMS)); } @Benchmark public void guavaMixed(Blackhole bh) { bh.consume(guavaEncode(MIXED_PARAMS)); } @Benchmark public void guavaLong(Blackhole bh) { bh.consume(guavaEncode(LONG_PARAMS)); } private static String guavaEncode(QueryParamGetters params) { final StringBuilder buf = TemporaryThreadLocals.get().stringBuilder(); for (Entry<String, String> e : params) { buf.append(guavaEscaper.escape(e.getKey())) .append('=') .append(guavaEscaper.escape(e.getValue())) .append('&'); } return buf.substring(0, buf.length() - 1); } @Benchmark public void nettyAscii(Blackhole bh) { bh.consume(nettyEncode(ASCII_PARAMS)); } @Benchmark public void nettyUnicode(Blackhole bh) { bh.consume(nettyEncode(UNICODE_PARAMS)); } @Benchmark public void nettyMixed(Blackhole bh) { bh.consume(nettyEncode(MIXED_PARAMS)); } @Benchmark public void nettyLong(Blackhole bh) { bh.consume(nettyEncode(LONG_PARAMS)); } private static String nettyEncode(QueryParamGetters params) { final QueryStringEncoder encoder = new QueryStringEncoder("", StandardCharsets.UTF_8); for (Entry<String, String> e : params) { encoder.addParam(e.getKey(), e.getValue()); } return encoder.toString(); } @Benchmark public void jdkAscii(Blackhole bh) throws UnsupportedEncodingException { bh.consume(jdkEncode(ASCII_PARAMS)); } @Benchmark public void jdkUnicode(Blackhole bh) throws UnsupportedEncodingException { bh.consume(jdkEncode(UNICODE_PARAMS)); } @Benchmark public void jdkMixed(Blackhole bh) throws UnsupportedEncodingException { bh.consume(jdkEncode(MIXED_PARAMS)); } @Benchmark public void jdkLong(Blackhole bh) throws UnsupportedEncodingException { bh.consume(jdkEncode(LONG_PARAMS)); } private static String jdkEncode(QueryParamGetters params) throws UnsupportedEncodingException { final StringBuilder buf = TemporaryThreadLocals.get().stringBuilder(); for (Entry<String, String> e : params) { buf.append(URLEncoder.encode(e.getKey(), "UTF-8")) .append('=') .append(URLEncoder.encode(e.getValue(), "UTF-8")) .append('&'); } return buf.substring(0, buf.length() - 1); } }