Kodumun Benchmark’ı

Furkan Şahin Kulaksız
12 min readNov 16, 2024

--

GÖRSELDEKI QR KODU KULLANARAK ILETISIM KANALLARIMDAN BANA ULASABILIRSINIZ

Benchmark nedir ve neden önemlidir.?

Benchmark, Türkçe’de “kıyas ölçütü” veya “karşılaştırma testi” olarak ifade edilir. Bilgisayar bilimlerinden ekonomiye kadar pek çok alanda kullanılan bir terimdir. Genel anlamda benchmark, bir ürünün, sistemin, yazılımın ya da işlemin performansını ölçmek ve belirli bir standarda göre karşılaştırmak amacıyla yapılan test veya değerlendirme sürecidir. Amaç, test edilen şeyin gücünü, hızını, etkinliğini veya kapasitesini ortaya koymaktır. Benchmark ile bir sistemin, uygulamanın veya cihazın hangi hızda çalıştığını, ne kadar verimli olduğunu ve maksimum ne kadar yük kaldırabileceğini görebiliriz. Örneğin Bir bilgisayarın CPU’su benchmark testine tabi tutulduğunda, o CPU’nun saniyede ne kadar işlem yapabildiği ölçülür.

Java’da benchmark yapılabilecek toolar ise aşağıdaki gibidir.

JMeter, VisualVM, Intellij’in Profiler’ı… Bunlardan bir tanesi de JMH’dir. (Java Microbenchmark Harness)

Low Level benchmark’lerde oldukça kullanışlıdır. JMH, JVM mühendisleri tarafından geliştirildiği için de gönül rahatlığıyla kullanılabilir.

Bu yazıda da JMH’ye uzunca bir bakış atacağız.

1.ADIM
Herhangi bir java maven projesi başlatıyorum ve pom.xml dosyasına aşağıdaki bağımlılıkları ekliyorum.

        <dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
</dependency>

<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
</dependency>

2.ADIM
Main class’ı içerisinde 2 farklı method tanımı yapıyorum. Birincisi normal String birleştirme yaparken, ikincisi StringBuilder ile birleştirme yapacak.

public static void stringConcatenation() {
String s = "";
for (int i = 0; i < 100; i++) {
s = s + i;
}
}

//-------------------------------------------------------

public void stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}

3. ADIM Film Arası (JMH’a Uzunca Bir Giriş)

State() Annotationu

Bu annotation JMH’da state yönetimi için kullanılır. Benchmark testlerimizde kullanacağımız değişkenleri ve nesneleri yönetmek için kullanılır. Yani test sırasında ihtiyaç duyduğumuz verileri tutmak için bir “container” görevi görür.

3 farklı parametre alabilir.

  • Scope.Thread: Her thread için ayrı bir instance oluşturulur.
  • Scope.Benchmark: Tüm threadler için tek bir instance oluşturulur.
  • Scope.Group: Thread groupları için ortak bir instance oluşturulur.
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;


@State(Scope.Thread)
public class Main {


public static void main(String[] args) {

}

public static void stringConcatenation() {
String s = "";
for (int i = 0; i < 100; i++) {
s = s + i;
}
}

public void stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}


}

Bu örnekte;

  • Her thread kendi numbers listesine sahip olacak
  • Threadler birbirlerinin listelerini etkilemeyecek
  • Bu sayede thread-safe bir test ortamı sağlanmış olur

Ne zaman, hangisi kullanılır.?

  • Scope.Thread Kullanılabilecek Durumlar
    ->
    Thread-safe test yapmak istiyorsanız
    -> Her thread’in kendi verisini işlemesini istiyorsanız
    -> Paralel testlerde veri karışmasını önlemek istiyorsanız
  • Scope.Benchmark Kullanılabilecek Durumlar
    ->
    Tüm threadler arasında veri paylaşımı gerekiyorsa
    -> Kaynaklar optimize edilmek isteniyorsa
    -> Threadler arası etkileşim test edilmek isteniyorsa
  • Scope.Group Kullanılabilecek Durumlar
    ->
    Belirli thread grupları arasında veri paylaşımı gerekiyorsa
    -> Karmaşık thread senaryoları test ediliyorsa

BenchmarkMode ve OutputTimeUnit Annotationları

BenchmarkMode:
JMH’de performans testlerimizin nasıl ölçüleceğini belirler. Yani testlerimizin hangi metrikle değerlendirileceğini tanımlar.

5 farklı parametre alabilir.

  • Throughput: Birim zamanda işlem süresi
  • AverageTime: Ortalama işlem süresi
  • SampleTime: Örnek zamanlama ölçümleri
  • SingleShotTime: Tek seferlik çalışma süresi
  • All: Tüm hepsini çalıştırır

OutputTimeUnit
Sonuçların hangi zaman biriminde rapor edileceğini belirlemek için varsayılan zaman birimini sağlar.

Bu anotasyon, yalnızca belirli bir @Benchmark annotationunda etkili olmak için o annotationa uygulanabilir veya tüm sınıf genelinde etkili olması için sınıfa uygulanabilir. Bu anotasyon, runtime seçenekleri ile geçersiz kılınabilir.

7 farklı parametre alabilir.

  • NANOSECONDS(TimeUnit.NANO_SCALE)
  • MICROSECONDS(TimeUnit.MICRO_SCALE)
  • MILLISECONDS(TimeUnit.MILLI_SCALE)
  • SECONDS(TimeUnit.SECOND_SCALE)
  • MINUTES(TimeUnit.MINUTE_SCALE)
  • HOURS(TimeUnit.HOUR_SCALE)
  • DAYS(TimeUnit.DAY_SCALE)

Örnekler

Throughput

// -> Saniyede kaç işlem yapılabildiğini ölçer
// -> Örnek sonuç: "1000 ops/sec" (saniyede 1000 işlem)
// -> Yüksek performanslı sistemlerde işlem kapasitesini ölçmek için kullanılabilir.

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class Example {

@Benchmark
public void test() {
// Kodlar
}

}

AverageTime

// -> Her işlemin ortalama ne kadar sürdüğünü ölçer
// -> Örnek sonuç: "100 μs/op" (işlem başına 100 mikrosaniye)
// -> İşlem süresinin önemli olduğu durumlarda kullanılabilir.

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class Example2 {

@Benchmark
public void test() {
// Kodlar
}
}

SampleTime

// -> Farklı çalışma sürelerinin dağılımını ölçer
// -> Performans Tutarlılığını ölçmek için kullanılabilir.

@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class Example3 {

@Benchmark
public void test() {
// Kodlar
}
}

SingleShotTime

// -> Kodu sadece bir kez çalıştırır ve süresini ölçer
// -> Başlangıç performansı ölçümlemek için kullanılabilir.

@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class Example4 {

@Benchmark
public void test() {
// Kodlar
}
}

Bizim örneğimize tekrar dönecek olursak, örneğin AverageTime’ı milliseconds ile ölçmek isteyelim.

import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class Main {

public static void main(String[] args) {

}

public static void stringConcatenation() {
String s = "";
for (int i = 0; i < 100; i++) {
s = s + i;
}
}

public void stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}

}

Kodu şu anlık çalıştırılabilir bir hale getirelim

Artık çıktıyı görebilmek için son eklemelerimizi yapalım ve detaylıca açıklayalım.

import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class Main {

public static void main(String[] args) {

Options opt = new OptionsBuilder()
.include(Main.class.getSimpleName())
.forks(1)
.warmupIterations(2)
.measurementIterations(3)
.build()

new Runner(opt).run();

}

@Benchmark
public static void stringConcatenation() {
String s = "";
for (int i = 0; i < 100; i++) {
s = s + i;
}
}

@Benchmark
public void stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}

}
  • İlk olarak teste sokmak istediğimiz methodları Benchmark annotationu ile imledik.
  • Sonrasında aşağıdaki kısmı ekledik.
Options opt = new OptionsBuilder()
.include(Main.class.getSimpleName())
.forks(1)
.warmupIterations(2)
.measurementIterations(3)
.build()

new Runner(opt).run();
  • include: Hangi sınıfların benchmark testlerinin çalıştırılacağını belirtir
    Bizim case’imizde yani şu anki kullanım durumunda “Main” içindeki tüm @Benchmark metodları çalıştırılır.
    Tabi farklı patternler ile de bunu sağlayabilirdik.
    .include(".*Test.*) Test içeren tüm sınıflar.
    .include("String.*")String ile başlayan tüm methodlar
  • forks: Benchmark’in kaç farklı JVM process’inde çalıştırılacağını belirtir. Her fork yeni bir JVM başlatır. JVM optimizasyonlarının etkisini ölçmek için kullanılır.
    .forks(0) Fork yok. Yani mevcut JVM’de çalışır.
    .forks(2) 2 farklı JVM’de çalışır.
    .forks(5) 5 farklı JVM’de çalışır
  • warmupIterations: JVM’in ısınma turlarının sayısını belirtir. JVM başlangıçta yavaştır. JIT compiler optimizasyonları için zaman gerekir. Gerçekçi sonuçlar için ısınma gereklidir. Yani aslında örnek olarak şunu söyleyebilliriz. Spora gidiyorsunuz ve önce ısınıyorsunuz. Çünkü ısınmadan spora başlarsanız hastanelik olursunuz. Sporunuz çöp olur. Ama ısınırsanız daha doğru ve gerçekçi bir spor yapmış olursunuz. Buradaki olay da aynı mantık diyebiliriz.
    .warmupIterations(0)ısınma yok.
    .warmupIterations(5)5 Isınma.
    .warmupIterations(TimeValue.seconds(10))10 saniyelik ısınma.
  • measurementIterations: Ölçüm sayısını belirtir. Çok basit bir matematikle 2 kere 2 dört ise daha fazla ölçüm ise daha doğru sonuç demektir. İstatistiksel anlamlılık için birden fazla ölçüm gerekir. measurementIterations(1)Tek ölçüm.
    measurementIterations(10)10 ölçüm.
    measurementIterations(TimeValue.seconds(5))5 saniyelik ölçüm.
  • Aşağıdkai ayarlamalarla da aslında farklı yöntemler ekleyebiliriz.
    thread(4)Kaç farklı thread kullanılacak.?
    resultFormat.(ResultFormatType.JSON).result("result.json)”sonuçları json formatında alabiliriz.
    .jsonArgs("-Xms2g","-Xmx2g")JVM Bellek ayarlarıyla oynayabiliriz.

Kodumuzdan öncelikle stringBuilder kısmını yorum satırlarına alıp bir çalıştıralım.

Kodu çalıştırdığımda;

# JMH version: 1.37
# VM version: JDK 17.0.9, OpenJDK 64-Bit Server VM, 17.0.9+8-LTS
# VM invoker: /Users/fsk/Library/Java/JavaVirtualMachines/corretto-17.0.9/Contents/Home/bin/java
# VM options: -javaagent:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=65433:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 2 iterations, 10 s each
# Measurement: 3 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.fsk.Main.stringConcatenation

# Run progress: 0.00% complete, ETA 00:00:50
# Fork: 1 of 1
# Warmup Iteration 1: 0.001 ms/op
# Warmup Iteration 2: 0.001 ms/op
Iteration 1: 0.001 ms/op
Iteration 2: 0.001 ms/op
Iteration 3: 0.001 ms/op


Result "org.fsk.Main.stringConcatenation":
0.001 ±(99.9%) 0.001 ms/op [Average]
(min, avg, max) = (0.001, 0.001, 0.001), stdev = 0.001
CI (99.9%): [0.001, 0.001] (assumes normal distribution)


# Run complete. Total time: 00:00:50

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.

Benchmark Mode Cnt Score Error Units
Main.stringConcatenation avgt 3 0.001 ± 0.001 ms/op

Yukarıdaki gibi bir sonuç aldım. Aslında bizim için önemli olabilecek bir kaç yer var.

Öncelikle logların en başında JVM options’ları veriyor. Burası bizim için önemli olabilir.

Orta kısımda warmup iteration’u bizler için veriyor. Burası da bizim için önemli olabilir.

Notlarda ise kısaca Profiler kullanın diyor. (((:

En aşağıda ise benchMark sonuçlarını veriyor. Burası ise tam olarak ilgileneceğimiz kısım.

Mode olarak average time verilmiş.
CNT dediği şey aslında count. 3 tane ölçüm yapılmış.
Score Ortalama sonuç. Bu işlem 0.001 sn’de tamamlanmış.
Error dediği şey +- 0.001 error payı ile tamamlanmış.
Units ise çıktının değeri. İşlem başına milisaniye.

Kodu tamamen açalım ve aşağıdaki hale getirip run edelim.

package org.fsk;

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class Main {
public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder()
.include(Main.class.getSimpleName())
.forks(1)
.warmupIterations(2)
.measurementIterations(3)
.resultFormat(ResultFormatType.JSON)
.result("result.json")
.build();

new Runner(opt).run();
}

@Benchmark
public static void stringConcatenation() {
String s = "";
for (int i = 0; i < 100; i++) {
s = s + i;
}
}

@Benchmark
public static void stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}
}

Bu kodu çalıştırdığımda result.json diye bir dosya oluşacak.

Bu dosyanın içeriği ise bende aşağıdaki gibi.

[
{
"jmhVersion" : "1.37",
"benchmark" : "org.fsk.Main.stringBuilder",
"mode" : "avgt",
"threads" : 1,
"forks" : 1,
"jvm" : "/Users/fsk/Library/Java/JavaVirtualMachines/corretto-17.0.9/Contents/Home/bin/java",
"jvmArgs" : [
"-javaagent:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=50306:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "17.0.9",
"vmName" : "OpenJDK 64-Bit Server VM",
"vmVersion" : "17.0.9+8-LTS",
"warmupIterations" : 2,
"warmupTime" : "10 s",
"warmupBatchSize" : 1,
"measurementIterations" : 3,
"measurementTime" : "10 s",
"measurementBatchSize" : 1,
"primaryMetric" : {
"score" : 2.901448025677078E-4,
"scoreError" : 6.274988071944121E-6,
"scoreConfidence" : [
2.838698144957637E-4,
2.964197906396519E-4
],
"scorePercentiles" : {
"0.0" : 2.897835501138222E-4,
"50.0" : 2.9018250801058574E-4,
"90.0" : 2.9046834957871553E-4,
"95.0" : 2.9046834957871553E-4,
"99.0" : 2.9046834957871553E-4,
"99.9" : 2.9046834957871553E-4,
"99.99" : 2.9046834957871553E-4,
"99.999" : 2.9046834957871553E-4,
"99.9999" : 2.9046834957871553E-4,
"100.0" : 2.9046834957871553E-4
},
"scoreUnit" : "ms/op",
"rawData" : [
[
2.897835501138222E-4,
2.9018250801058574E-4,
2.9046834957871553E-4
]
]
},
"secondaryMetrics" : {
}
},
{
"jmhVersion" : "1.37",
"benchmark" : "org.fsk.Main.stringConcatenation",
"mode" : "avgt",
"threads" : 1,
"forks" : 1,
"jvm" : "/Users/fsk/Library/Java/JavaVirtualMachines/corretto-17.0.9/Contents/Home/bin/java",
"jvmArgs" : [
"-javaagent:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=50306:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "17.0.9",
"vmName" : "OpenJDK 64-Bit Server VM",
"vmVersion" : "17.0.9+8-LTS",
"warmupIterations" : 2,
"warmupTime" : "10 s",
"warmupBatchSize" : 1,
"measurementIterations" : 3,
"measurementTime" : "10 s",
"measurementBatchSize" : 1,
"primaryMetric" : {
"score" : 0.0011059989670379143,
"scoreError" : 2.898817264418705E-4,
"scoreConfidence" : [
8.161172405960438E-4,
0.0013958806934797848
],
"scorePercentiles" : {
"0.0" : 0.0010901537231213653,
"50.0" : 0.001105911028711041,
"90.0" : 0.0011219321492813368,
"95.0" : 0.0011219321492813368,
"99.0" : 0.0011219321492813368,
"99.9" : 0.0011219321492813368,
"99.99" : 0.0011219321492813368,
"99.999" : 0.0011219321492813368,
"99.9999" : 0.0011219321492813368,
"100.0" : 0.0011219321492813368
},
"scoreUnit" : "ms/op",
"rawData" : [
[
0.0010901537231213653,
0.001105911028711041,
0.0011219321492813368
]
]
},
"secondaryMetrics" : {
}
}
]

İşte burası bizim için çok çok önemli. Sadece rawData’lara odaklanıcam ve Burada chatGPT üzerinden bir karşılaştırma yapıcam.

Üstteki sayı stringConcenetation methodunun ilk ölçüm değeri sonucu. İkinci sayı ise stringBuilder methodunun sonucu.

Annotation’lar ile Yönetmek

Options içerisine yaptıklarımızı annotation’lar ile de yapabilirdik.

package org.fsk;

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.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;

import java.io.IOException;
import java.util.concurrent.TimeUnit;


@State(Scope.Thread)
@Fork(value = 2)
@Warmup(iterations = 2)
@Measurement(iterations = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Main2 {

public static void main(String[] args) throws IOException {
org.openjdk.jmh.Main.main(args);
}

@Benchmark
public static void stringConcatenation() {
String s = "";
for (int i = 0; i < 100; i++) {
s = s + i;
}
}

@Benchmark
public static void stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}

}

Ben bunu nerede kullanacağım.?

Kurumsal firmalarda ya da start-up’da çalışıyorsanız ve size “haydi şu methodu bir benchmark testine sok ve sonuçları görelim” diye bir task geldiğinde tabiiki bu yöntemi kullanmak çok doğru mudur, bilemem…

Belki Profiler kullanmak, tüm sistemi analiz etmek, daha doğru bir yaklaşım olacaktır.

Ama mesela ArrayList ve LinkedList sınıfını karşılaştırmak istedim. Bu benchmark yöntemi ile deneysel olarak aradaki farkları görebiliriz. Yani şunu demeye çalışıyorum, ufak deneysellikte performans farını ölçebileceğimiz her şeyde bunu kullanabiliriz.

Kendi adıma konuşayım. Ben Data Structures ve Algorithms üzerine biraz takık birisiyim. LeetCode’dan olabildiğince örnekler çözüyorum.

Bundan bir kaç ay önce yaklaşık 15 saatlik “Data Structures and Algorithms” eğitimi yayınladım. Bu eğitimde bir algoritma sorusunu bir kaç farklı yöntemle çözdüm ve aralarındaki performans farklarını inceledim. Eğitimde kapsamın dışına çıkmamak için, methodun başına ve sonuna timer’lar koyup bu timer’ları karşılaştıran bir generic class yazdım.

(Eğitimi incelemek ve satın almak için buraya tıklayabilirsiniz.)

Ama ben, bu yöntemi kullanarak leetcode’da soru çözdüğümde artık performans farklarını inceleyeceğim.

Örneğin;

package org.fsk;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


@State(value = Scope.Thread)
@BenchmarkMode(value = Mode.AverageTime)
@OutputTimeUnit(value = TimeUnit.MILLISECONDS)
public class Main3 {

public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder()
.include(Main3.class.getSimpleName())
.forks(1)
.warmupIterations(2)
.measurementIterations(3)
.resultFormat(ResultFormatType.JSON)
.result("result2.json")
.build();

new Runner(opt).run();

}

@Benchmark
public static List<Integer> pythagorasTriples1() {

List<Integer> triples = new ArrayList<>();

int total = 1000;

for (int a = 1; a <= total; a++) {
for (int b = 1; b <= total; b++) {
for (int c = 1; c <= total; c++) {
if (
((a < b) && (a < c) && (b < c) &&
((a*a) + (b*b) == (c*c))) &&
(a + b + c) == total
) {
triples.add(a);
triples.add(b);
triples.add(c);
}
}
}
}
return triples;
}

@Benchmark
public static List<Integer> pythagorasTriples2() {

List<Integer> triples = new ArrayList<>();

int total = 1000;

for (int a = 1; a < total / 3; a++) {
for (int b = a + 1; b < total / 2; b++) {
int c = total - a - b;
if ((a * a) + (b * b) == (c * c)) {
triples.add(a);
triples.add(b);
triples.add(c);
}
}
}
return triples;
}
}

Bu kod, “toplamları 1000 olan Pisagor Üçlü’sünü bulan kod”. 2 farklı yaklaşımla çözdüm. Çok az bir modifiye ile alttaki methodu oluşturdum ve arasındaki performans farkına bakalım.

[
{
"jmhVersion" : "1.37",
"benchmark" : "org.fsk.Main3.pythagorasTriples1",
"mode" : "avgt",
"threads" : 1,
"forks" : 1,
"jvm" : "/Users/fsk/Library/Java/JavaVirtualMachines/corretto-17.0.9/Contents/Home/bin/java",
"jvmArgs" : [
"-javaagent:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=58608:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "17.0.9",
"vmName" : "OpenJDK 64-Bit Server VM",
"vmVersion" : "17.0.9+8-LTS",
"warmupIterations" : 2,
"warmupTime" : "10 s",
"warmupBatchSize" : 1,
"measurementIterations" : 3,
"measurementTime" : "10 s",
"measurementBatchSize" : 1,
"primaryMetric" : {
"score" : 256.9439631559829,
"scoreError" : 30.380596664241903,
"scoreConfidence" : [
226.563366491741,
287.3245598202248
],
"scorePercentiles" : {
"0.0" : 255.02701875,
"50.0" : 257.77169123076925,
"90.0" : 258.0331794871795,
"95.0" : 258.0331794871795,
"99.0" : 258.0331794871795,
"99.9" : 258.0331794871795,
"99.99" : 258.0331794871795,
"99.999" : 258.0331794871795,
"99.9999" : 258.0331794871795,
"100.0" : 258.0331794871795
},
"scoreUnit" : "ms/op",
"rawData" : [
[
258.0331794871795,
257.77169123076925,
255.02701875
]
]
},
"secondaryMetrics" : {
}
},
{
"jmhVersion" : "1.37",
"benchmark" : "org.fsk.Main3.pythagorasTriples2",
"mode" : "avgt",
"threads" : 1,
"forks" : 1,
"jvm" : "/Users/fsk/Library/Java/JavaVirtualMachines/corretto-17.0.9/Contents/Home/bin/java",
"jvmArgs" : [
"-javaagent:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/lib/idea_rt.jar=58608:/Users/fsk/Applications/IntelliJ IDEA Ultimate.app/Contents/bin",
"-Dfile.encoding=UTF-8"
],
"jdkVersion" : "17.0.9",
"vmName" : "OpenJDK 64-Bit Server VM",
"vmVersion" : "17.0.9+8-LTS",
"warmupIterations" : 2,
"warmupTime" : "10 s",
"warmupBatchSize" : 1,
"measurementIterations" : 3,
"measurementTime" : "10 s",
"measurementBatchSize" : 1,
"primaryMetric" : {
"score" : 0.24252069409589336,
"scoreError" : 0.018007404098752667,
"scoreConfidence" : [
0.22451328999714068,
0.260528098194646
],
"scorePercentiles" : {
"0.0" : 0.24141835171198997,
"50.0" : 0.24282108270001215,
"90.0" : 0.24332264787567792,
"95.0" : 0.24332264787567792,
"99.0" : 0.24332264787567792,
"99.9" : 0.24332264787567792,
"99.99" : 0.24332264787567792,
"99.999" : 0.24332264787567792,
"99.9999" : 0.24332264787567792,
"100.0" : 0.24332264787567792
},
"scoreUnit" : "ms/op",
"rawData" : [
[
0.24141835171198997,
0.24332264787567792,
0.24282108270001215
]
]
},
"secondaryMetrics" : {
}
}
]

Methodlardaki raw data’lara odaklandığımızda çarpıcı sonuç ayan beyan ortada.

Okuduğunuz için teşekkür ederim. Kaynak kodlarına buradan ulaşabilirsiniz.

İletişim, İşbirlikleri ve Teklifler için,

github: https://github.com/fsk

bitbucket: https://bitbucket.org/furkandev

twitter: https://twitter.com/0xfsk

mail: furkansahinkulaksiz@gmail.com

linkedIn: https://www.linkedin.com/in/frknshnklksz/

superpeer: https://superpeer.com/fsk

--

--

No responses yet