{"id":146,"date":"2024-07-20T17:23:39","date_gmt":"2024-07-20T20:23:39","guid":{"rendered":"https:\/\/alice.com.br\/tech\/sem-categoria\/como-fazemos-microbenchmarks-em-kotlin\/"},"modified":"2026-02-05T10:20:29","modified_gmt":"2026-02-05T13:20:29","slug":"como-fazemos-microbenchmarks-em-kotlin","status":"publish","type":"post","link":"https:\/\/alice.com.br\/tech\/como-fazemos-microbenchmarks-em-kotlin\/","title":{"rendered":"Como fazemos microbenchmarks em Kotlin"},"content":{"rendered":"<p>As vezes precisamos fazer alguns testes de performance ou de benchmark, a fim de ter um melhor embasamento na hora de <a href=\"https:\/\/medium.com\/alice-tech\/como-tomamos-decis%C3%B5es-860121def07f\">tomar nossas decis\u00f5es<\/a>. Dado que n\u00e3o existe muito material sobre o assunto com essa stack espec\u00edfica, pensamos em descrever aqui como fizemos nossa aplica\u00e7\u00e3o simples para benchmarks.<\/p>\n<h3>Afinal, o que \u00e9 um microbenchmark?<\/h3>\n<p>Um microbenchmark \u00e9 um teste projetado para medir o desempenho de uma unidade muito pequena: o tempo para chamar um m\u00e9todo sincronizado versus um m\u00e9todo n\u00e3o sincronizado; a sobrecarga na cria\u00e7\u00e3o de um encadeamento versus o uso de um conjunto de encadeamentos; o tempo para executar um algoritmo aritm\u00e9tico versus uma implementa\u00e7\u00e3o alternativa; e assim por\u00a0diante.<\/p>\n<p>\u00c9 uma forma bem interessante de verificar se algum fluxo que estamos fazemos est\u00e1 consumindo muito ou pouco tempo ou recursos (CPU, mem\u00f3ria, rede, disco, etc). Por\u00e9m deve ser feito com\u00a0cautela.<\/p>\n<p>Muitos fatores podem interferir na execu\u00e7\u00e3o e aferi\u00e7\u00e3o de um benchmark, como capacidade de processamento do computador, mem\u00f3ria dispon\u00edvel, vers\u00e3o do framework, entre outros. Por conta disso, deve ser feito para testes isolados de unidades bem espec\u00edficas.<\/p>\n<h3>Testando uso de reflections para nossos\u00a0logs<\/h3>\n<p>H\u00e1 alguns meses atr\u00e1s, o time <a href=\"https:\/\/alice.com.br\/blog\/conheca-a-alice\/alice-e-a-empresa-mais-inovadora-da-america-latina-segundo-fast-company\/\">Alice<\/a> estudou a necessidade do uso de <em>Reflection<\/em> para identificar a fun\u00e7\u00e3o e a classe exata de onde o log foi\u00a0gerado.<\/p>\n<p>Nossa hip\u00f3tese era que a utiliza\u00e7\u00e3o de <em>Reflection<\/em> poderia aumentar o consumo de recursos da fun\u00e7\u00e3o, mas n\u00e3o sab\u00edamos o quanto aumentaria e se isso impactaria de verdade nossas aplica\u00e7\u00f5es.<\/p>\n<h4>Importa\u00e7\u00e3o de depend\u00eancias<\/h4>\n<p>Usamos a biblioteca JMH (the Java Microbenchmark Harness) para fazer a compara\u00e7\u00e3o. Utilizamos tanto o <a href=\"https:\/\/search.maven.org\/classic\/#artifactdetails%7Corg.openjdk.jmh%7Cjmh-core%7C1.19%7Cjar\">JMH Core<\/a> quanto <a href=\"https:\/\/search.maven.org\/classic\/#artifactdetails%7Corg.openjdk.jmh%7Cjmh-generator-annprocess%7C1.19%7Cjar\">JMH Annotation Processor<\/a>.<\/p>\n<pre>plugins <strong>{\r\n    <\/strong><em>kotlin<\/em>(\"jvm\") <em>version <\/em>\"1.4.32\"\r\n    <em>kotlin<\/em>(\"kapt\") <em>version <\/em>\"1.4.32\"\r\n    id(\"me.champeau.gradle.jmh\") <em>version <\/em>\"0.5.2\"\r\n    id(\"io.morethan.jmhreport\") <em>version <\/em>\"0.9.0\"\r\n    <em>application\r\n<\/em><strong>}<\/strong><\/pre>\n<p>No build.gradle.kts do projeto n\u00f3s declaramos a task para rodar os testes de benchmark<\/p>\n<pre><em>task<\/em>(\"benchmarks\", type = JavaExec::class) <strong>{\r\n  <\/strong><em>classpath <\/em>= <em>sourceSets<\/em>.getByName(\"test\").<em>runtimeClasspath\r\n  main <\/em>= \"br.com.alice.benchmarks.BenchmarksRunnerKt\"\r\n<strong>}<\/strong><\/pre>\n<p>Al\u00e9m disso importamos a biblioteca orgs.slf4j.Logger para criar nossos logs. A lista de depend\u00eancias ficou:<\/p>\n<pre><em>dependencies <\/em><strong>{\r\n    <\/strong><em>kapt<\/em>(\"org.openjdk.jmh:jmh-generator-annprocess:1.25\")\r\n\r\n    <em>implementation<\/em>(\"org.openjdk.jmh:jmh-core:1.25\")\r\n    <em>implementation<\/em>(\"org.openjdk.jmh:jmh-generator-annprocess:1.25\")\r\n\r\n    <em>api<\/em>(\"ch.qos.logback:logback-classic:1.2.3\")\r\n    <em>implementation<\/em>(\"net.logstash.logback:logstash-logback-encoder:6.4\")\r\n\r\n    <em>annotationProcessor<\/em>(\"org.openjdk.jmh:jmh-generator-annprocess:1.25\")\r\n\r\n    <em>testImplementation<\/em>(<em>kotlin<\/em>(\"test-junit\"))\r\n<strong>}<\/strong><\/pre>\n<h4>Cria\u00e7\u00e3o da fun\u00e7\u00e3o de\u00a0log<\/h4>\n<p>Para realizar o teste, criamos uma abstra\u00e7\u00e3o para a biblioteca orgs.slf4j.Logger\u00a0. S\u00f3 possui uma simples fun\u00e7\u00e3o de logar info, simulando nossa biblioteca interna para\u00a0logs.<\/p>\n<p><iframe loading=\"lazy\" src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https:\/\/medium.com\/media\/e613b3205a2f958257f256880d60dc51\/href\">https:\/\/medium.com\/media\/e613b3205a2f958257f256880d60dc51\/href<\/a><\/iframe><\/p>\n<h4>Criando os casos de\u00a0teste<\/h4>\n<p>JMH suporta diferentes tipos de benchmarks: <em>Throughput,<\/em> <em>AverageTime,<\/em> <em>SampleTime<\/em>, and <em>SingleShotTime<\/em>. S\u00e3o configurados via annotation @BenchmarkMode\u00a0.<\/p>\n<ul>\n<li><em>Throughput<\/em>: nesse modo, o teste \u00e9 executado em um per\u00edodo pre determinado e \u00e9 avaliado o n\u00famero de vezes que o m\u00e9todo em teste foi executado<\/li>\n<li><em>AverageTime<\/em>: inverso ao <em>Throughput<\/em>, esse m\u00e9todo \u00e9 usado para se ter o tempo m\u00e9dio de cada chamada do m\u00e9todo em\u00a0teste.<\/li>\n<li><em>SampleTime<\/em>: esse m\u00e9todo tamb\u00e9m utiliza um tempo pr\u00e9 determinado, por\u00e9m aqui algumas chamadas s\u00e3o selecionadas randomicamente e seu tempo avaliado.<\/li>\n<li><em>SingleShotTime<\/em>: mede o tempo de uma \u00fanica\u00a0chamada.<\/li>\n<\/ul>\n<p>Para o nosso caso, utilizamos <em>AverageTime<\/em>, dessa forma a gente simular os nossos micro-servi\u00e7os utilizando os m\u00e9todos de log diversas vezes e ver o quanto isso impactaria o tempo e os recursos dos\u00a0mesmos.<\/p>\n<p>Configuramos tamb\u00e9m a unidade de medida temporal para microsegundos, tamb\u00e9m via annotation @OutputTimeUnit\u00a0.<\/p>\n<p>Usamos a annotation @Benchmark para definir uma fun\u00e7\u00e3o como <em>test case<\/em> do benchmarking. Criamos 3 tipos de\u00a0testes:<\/p>\n<ul>\n<li>Log sem nenhuma reflection<\/li>\n<li>Log utilizando reflection atrav\u00e9s do Throwable para receber os nomes de classe e\u00a0m\u00e9todo.<\/li>\n<li>Log utilizando reflection atrav\u00e9s da Thread para receber os nomes de classe e\u00a0m\u00e9todo.<\/li>\n<\/ul>\n<p><iframe loading=\"lazy\" src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https:\/\/medium.com\/media\/d4d08cafb3722fed8a0ab3599682a6db\/href\">https:\/\/medium.com\/media\/d4d08cafb3722fed8a0ab3599682a6db\/href<\/a><\/iframe><\/p>\n<h4>Configura\u00e7\u00e3o final do benchmark<\/h4>\n<p>Para agrupar todos os testes, configurar sa\u00edda do resultado e definir \u00faltimas configura\u00e7\u00f5es, n\u00f3s criamos o arquivo <strong>BenchmarksRunner.kt<\/strong>, assim declarado no gradle.build.kts\u00a0, mostrado no in\u00edcio do\u00a0artigo.<\/p>\n<p><iframe loading=\"lazy\" src=\"\" width=\"0\" height=\"0\" frameborder=\"0\" scrolling=\"no\"><a href=\"https:\/\/medium.com\/media\/cf5835559cc5bcb2f4e16b799c9127f2\/href\">https:\/\/medium.com\/media\/cf5835559cc5bcb2f4e16b799c9127f2\/href<\/a><\/iframe><\/p>\n<p>As configura\u00e7\u00f5es utilizadas no OptionsBuilder:<\/p>\n<ul>\n<li>include adiciona o teste que vamos realizar. Nele indicamos qual a classe a ser executada, nesse caso o LogReflectionBenchmark\u00a0.<\/li>\n<li>forks \u00e9 o n\u00famero de testes paralelos que v\u00e3o ser executados em cada itera\u00e7\u00e3o. Utilizamos 2\u00a0forks.<\/li>\n<li>threads \u00e9 o n\u00famero de threads a ser utilizada para cada teste. Escolhemos duas threads. Dependendo do n\u00famero de threads dispon\u00edveis na m\u00e1quina, esse valor pode aumentar.<\/li>\n<li>warmupBatchSize \u00e9 o tamanho o grupo de aquecimento. Utilizamos 2\u00a0<em>batches<\/em>.<\/li>\n<li>warmupIterations \u00e9 o n\u00famero de itera\u00e7\u00f5es a serem executadas antes de come\u00e7ar a contabilizar o tempo. Esse n\u00famero \u00e9 importante pois ajuda a contabilizar depois que a m\u00e1quina j\u00e1 estiver rodando os testes. Um n\u00famero muito alto pode impactar negativamente o teste, se o processo j\u00e1 tiver consumido bastante recurso da\u00a0m\u00e1quina.<\/li>\n<li>measurementIterations \u00e9 o n\u00famero de itera\u00e7\u00f5es, agora contabilizando o tempo de cada teste. O resultado final \u00e9 montado a partir a m\u00e9dia entre as diferentes itera\u00e7\u00f5es.<\/li>\n<\/ul>\n<p>Al\u00e9m disso, definimos como arquivos output benchmark_output.log e benchmark_result.json\u00a0. Eles v\u00e3o conter as informa\u00e7\u00f5es dos\u00a0testes.<\/p>\n<h4>Hora de rodar os\u00a0testes<\/h4>\n<p>Depois de toda a configura\u00e7\u00e3o, podemos rodar o BenchmarksRunner.main e aguardar os resultados. Ap\u00f3s alguns minutos, o resultado foi mostrado no arquivo benchmark_output.log\u00a0.<\/p>\n<pre>LogReflectionBenchmark.logWithReflectionThreadGetStackTrace     avgt    4  28.860 \u00b1 7.172  us\/op\r\nLogReflectionBenchmark.logWithReflectionThrowableGetStackTrace  avgt    4  42.075 \u00b1 11.087  us\/op\r\nLogReflectionBenchmark.logWithoutReflection                     avgt    4  0.36028 \u00b1  0.00035  us\/op<\/pre>\n<p>Como voc\u00eas podem ver, a utiliza\u00e7\u00e3o do log sem nenhum tipo de <em>reflection<\/em> foi cerca de 100x mais r\u00e1pida do que com uso de qualquer <em>reflection<\/em>. Isso j\u00e1 foi necess\u00e1rio para que o nosso time decidisse n\u00e3o precisar logar o nome da fun\u00e7\u00e3o e nem da\u00a0classe.<\/p>\n<h3>Teste voc\u00ea\u00a0mesmo<\/h3>\n<p>Caso queira validar tamb\u00e9m em sua m\u00e1quina, o c\u00f3digo de exemplo est\u00e1 aberto. Voc\u00ea pode fazer o mesmo teste que n\u00f3s fizemos, ou usar o exemplo para criar seus pr\u00f3prios microbenchmarks em\u00a0Kotlin.<\/p>\n<p><a href=\"https:\/\/github.com\/alice-health\/kotlin-benchmark-example\">alice-health\/kotlin-benchmark-example<\/a><\/p>\n<p>No reposit\u00f3rio tem a descri\u00e7\u00e3o de como executar os testes. E se voc\u00ea conseguiu testar outros <em>use cases<\/em>, compartilha aqui com a\u00a0gente!<\/p>\n<h3>Que tal fazer parte desse\u00a0time?<\/h3>\n<p>Estamos buscando pessoas que topem o desafio de transformar a sa\u00fade no Brasil atrav\u00e9s da tecnologia. <a href=\"https:\/\/www.alice.com.br\/carreiras\">Clica aqui<\/a> para saber mais das vagas que temos em\u00a0aberto!<\/p>\n","protected":false},"excerpt":{"rendered":"As vezes precisamos fazer alguns testes de performance ou de benchmark, a fim de ter um melhor embasamento na hora de tomar nossas decis\u00f5es.","protected":false},"author":3,"featured_media":181,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-146","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software"],"acf":[],"_links":{"self":[{"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/posts\/146","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/comments?post=146"}],"version-history":[{"count":3,"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/posts\/146\/revisions"}],"predecessor-version":[{"id":264,"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/posts\/146\/revisions\/264"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/media\/181"}],"wp:attachment":[{"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/media?parent=146"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/categories?post=146"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/alice.com.br\/tech\/wp-json\/wp\/v2\/tags?post=146"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}