Skip to content

Commit

Permalink
Proto optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
szeiger committed Nov 21, 2024
1 parent 9310979 commit 766463d
Show file tree
Hide file tree
Showing 28 changed files with 433 additions and 164 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package perfio

import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*
import perfio.internal.BufferUtil

import java.util.concurrent.TimeUnit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package perfio

import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*
import perfio.internal.BufferUtil

import java.io.*
import java.nio.ByteBuffer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,21 @@ class BufferedOutputStringBenchmark extends BenchUtil:
bh.consume(bout.getBuffer)

@Benchmark
def array_FullyBufferedOutput_growing(bh: Blackhole): Unit =
def array_ArrayBufferedOutput_growing(bh: Blackhole): Unit =
val out = BufferedOutput.growing()
writeTo(out)
bh.consume(out.buffer)
bh.consume(out.length)

@Benchmark
def array_FullyBufferedOutput_growing_preallocated(bh: Blackhole): Unit =
def array_ArrayBufferedOutput_growing_preallocated(bh: Blackhole): Unit =
val out = BufferedOutput.growing(totalLength)
writeTo(out)
bh.consume(out.buffer)
bh.consume(out.length)

@Benchmark
def array_FullyBufferedOutput_fixed(bh: Blackhole): Unit =
def array_ArrayBufferedOutput_fixed(bh: Blackhole): Unit =
val out = BufferedOutput.ofArray(new Array[Byte](totalLength))
writeTo(out)
bh.consume(out.buffer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package perfio

import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*
import perfio.internal.BufferUtil

import java.util.concurrent.TimeUnit

Expand Down
1 change: 1 addition & 0 deletions bench/src/main/scala/perfio/LineTokenizerBenchmark.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package perfio

import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*
import perfio.internal.StringInternals

import java.io.*
import java.lang.foreign.MemorySegment
Expand Down
1 change: 1 addition & 0 deletions bench/src/main/scala/perfio/TextOutputBenchmark.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package perfio

import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*
import perfio.internal.StringInternals

import java.io.*
import java.nio.charset.Charset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import java.nio.file.{Files, Path}
@Measurement(iterations = 7, time = 1)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
class ParseBenchmark:
class ReadBenchmark:

private var testData: Array[Byte] = null
private val testDataFile = Path.of("src/main/resources/CodeGeneratorRequest-pack.bin")
Expand Down
71 changes: 71 additions & 0 deletions proto-bench/src/main/scala/ReadWriteBenchmark.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package perfio;

import com.google.protobuf.compiler.PluginProtos as GPluginProtos
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*
import perfio.protoapi.PluginProtos as PPluginProtos

import java.io.{BufferedInputStream, BufferedOutputStream, ByteArrayOutputStream, FileInputStream, FileOutputStream}
import java.nio.ByteOrder
import java.nio.file.{Files, Path}
import java.util.concurrent.TimeUnit

@BenchmarkMode(Array(Mode.AverageTime))
@Fork(value = 1, jvmArgsAppend = Array("-Xmx12g", "-Xss32M", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseZGC", "--enable-native-access=ALL-UNNAMED", "--add-modules", "jdk.incubator.vector"))
@Threads(1)
@Warmup(iterations = 7, time = 1)
@Measurement(iterations = 7, time = 1)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
class ReadWriteBenchmark:

private var testData: Array[Byte] = null
private val testDataFile = Path.of("src/main/resources/CodeGeneratorRequest-pack.bin")

@Setup(Level.Trial)
def buildTestData(): Unit =
testData = Files.readAllBytes(testDataFile)
assert(testData.length == 159366, s"testData.length ${testData.length}")

@Benchmark
def array_google(bh: Blackhole): Unit =
val p = GPluginProtos.CodeGeneratorRequest.parseFrom(testData)
val out = new ByteArrayOutputStream()
p.writeTo(out)
out.close()
bh.consume(out.size())

@Benchmark
def array_perfio(bh: Blackhole): Unit =
val p = PPluginProtos.CodeGeneratorRequest.parseFrom(BufferedInput.ofArray(testData).order(ByteOrder.LITTLE_ENDIAN))
val bo = BufferedOutput.growing(4096)
p.writeTo(bo)
bo.close()
bh.consume(bo.length())

@Benchmark
def file_google(bh: Blackhole): Unit =
val in = new BufferedInputStream(new FileInputStream(testDataFile.toFile))
val p = GPluginProtos.CodeGeneratorRequest.parseFrom(in)
in.close()
val out = new BufferedOutputStream(new FileOutputStream("/dev/null"))
p.writeTo(out)
out.close()

@Benchmark
def file_perfio(bh: Blackhole): Unit =
val in = BufferedInput.of(new FileInputStream(testDataFile.toFile)).order(ByteOrder.LITTLE_ENDIAN)
val p = PPluginProtos.CodeGeneratorRequest.parseFrom(in)
in.close()
val bo = BufferedOutput.ofFile(Path.of("/dev/null"), 8192)
p.writeTo(bo)
bo.close()

@Benchmark
def file_perfio_mapped(bh: Blackhole): Unit =
val in = BufferedInput.ofMappedFile(testDataFile).order(ByteOrder.LITTLE_ENDIAN)
val p = PPluginProtos.CodeGeneratorRequest.parseFrom(in)
in.close()
val bo = BufferedOutput.ofFile(Path.of("/dev/null"), 8192)
p.writeTo(bo)
bo.close()
114 changes: 114 additions & 0 deletions proto-bench/src/main/scala/SmallWriteBenchmark.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package perfio

import com.google.protobuf.compiler.PluginProtos as GPluginProtos
import com.google.protobuf.{CodedOutputStream, GeneratedMessage as GGeneratedMessage, Parser as GParser}
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*
import perfio.proto.runtime.GeneratedMessage as PGeneratedMessage
import perfio.protoapi.PluginProtos as PPluginProtos

import java.io.{BufferedOutputStream, ByteArrayOutputStream, FileOutputStream, OutputStream}
import java.lang.invoke.{MethodHandles, MethodType}
import java.nio.file.Path
import java.util.concurrent.TimeUnit

@BenchmarkMode(Array(Mode.AverageTime))
@Fork(value = 1, jvmArgsAppend = Array("-Xmx12g", "-Xss32M", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseZGC", "--enable-native-access=ALL-UNNAMED", "--add-modules", "jdk.incubator.vector"))
@Threads(1)
@Warmup(iterations = 15, time = 1)
@Measurement(iterations = 10, time = 1)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
class SmallWriteBenchmark:

@Param(Array("version1000"))
var testCase: String = null

private var gMessage: GGeneratedMessage = null
private var pMessage: PGeneratedMessage = null
private var count = 1;

private def p2g(p: PGeneratedMessage, gp: GParser[? <: GGeneratedMessage]): GGeneratedMessage = {
val out = BufferedOutput.growing()
p.writeTo(out)
out.close()
gp.parseFrom(out.toInputStream)
}

val clearMemoMethod = {
val lookup = MethodHandles.privateLookupIn(classOf[GGeneratedMessage], MethodHandles.lookup())
lookup.findVirtual(classOf[GGeneratedMessage], "setMemoizedSerializedSize", MethodType.methodType(Void.TYPE, Integer.TYPE))
}

private def clearMemo(m: GGeneratedMessage): Unit = {
clearMemoMethod.invokeExact(m, -1)
}

@Setup(Level.Trial)
def buildInvocationData(): Unit =
testCase match
case "version1000" =>
pMessage = (new PPluginProtos.Version).setMajor(1).setMinor(2).setPatch(3) //.setSuffix("foo")
gMessage = p2g(pMessage, GPluginProtos.Version.parser())
count = 1000
case "versionString1000" =>
pMessage = (new PPluginProtos.Version).setMajor(1).setMinor(2).setPatch(3).setSuffix("foo")
gMessage = p2g(pMessage, GPluginProtos.Version.parser())
count = 1000
case "versionString1" =>
pMessage = (new PPluginProtos.Version).setMajor(1).setMinor(2).setPatch(3).setSuffix("foo")
gMessage = p2g(pMessage, GPluginProtos.Version.parser())
count = 1
case "strings" =>
val r = new PPluginProtos.CodeGeneratorRequest
for(i <- 1 to 1000) r.addFileToGenerate(s"file $i")
pMessage = r
gMessage = p2g(pMessage, GPluginProtos.CodeGeneratorRequest.parser())
count = 10
case "nested" =>
val r = new PPluginProtos.CodeGeneratorRequest
r.setCompilerVersion((new PPluginProtos.Version).setMajor(1).setMinor(2).setPatch(3).setSuffix("foo"))
pMessage = r
gMessage = p2g(pMessage, GPluginProtos.CodeGeneratorRequest.parser())
count = 100

private def writeGoogle(out: OutputStream): Unit =
val c = CodedOutputStream.newInstance(out, 4096)
var i = 0
while i < count do
//clearMemo(gMessage.asInstanceOf[GPluginProtos.CodeGeneratorRequest].getCompilerVersion)
gMessage.writeTo(c)
i += 1
c.flush()
out.close()

private def writePerfio(out: BufferedOutput): Unit =
var i = 0
while i < count do
pMessage.writeTo(out)
i += 1
out.close()

@Benchmark
def array_google(bh: Blackhole): Unit =
val baos = new ByteArrayOutputStream()
writeGoogle(baos)
bh.consume(baos.size())

@Benchmark
def array_perfio(bh: Blackhole): Unit =
val bo = BufferedOutput.growing(4096)
writePerfio(bo)
bh.consume(bo.length())

@Benchmark
def file_google(bh: Blackhole): Unit =
val out = new BufferedOutputStream(new FileOutputStream("/dev/null"))
writeGoogle(out)
out.close()

@Benchmark
def file_perfio(bh: Blackhole): Unit =
val bo = BufferedOutput.ofFile(Path.of("/dev/null"), 8192)
writePerfio(bo)
bo.close()
70 changes: 51 additions & 19 deletions proto-bench/src/main/scala/WriteBenchmark.scala
Original file line number Diff line number Diff line change
@@ -1,59 +1,91 @@
package perfio

import org.openjdk.jmh.annotations._
import org.openjdk.jmh.infra._
import com.google.protobuf.{CodedOutputStream, GeneratedMessage as GGeneratedMessage, Parser as GParser}
import perfio.proto.runtime.GeneratedMessage as PGeneratedMessage
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.*

import java.util.concurrent.TimeUnit
import com.google.protobuf.compiler.{PluginProtos => GPluginProtos}
import perfio.protoapi.{PluginProtos => PPluginProtos}
import com.google.protobuf.compiler.PluginProtos as GPluginProtos
import perfio.protoapi.PluginProtos as PPluginProtos

import java.io.{BufferedOutputStream, ByteArrayOutputStream, FileOutputStream}
import java.io.{BufferedOutputStream, ByteArrayOutputStream, FileOutputStream, OutputStream}
import java.lang.invoke.{MethodHandles, MethodType}
import java.nio.ByteOrder
import java.nio.file.{Files, Path}

import scala.jdk.CollectionConverters._

@BenchmarkMode(Array(Mode.AverageTime))
@Fork(value = 1, jvmArgsAppend = Array("-Xmx12g", "-Xss32M", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseZGC", "--enable-native-access=ALL-UNNAMED", "--add-modules", "jdk.incubator.vector"))
@Threads(1)
@Warmup(iterations = 7, time = 1)
@Measurement(iterations = 7, time = 1)
@Warmup(iterations = 15, time = 1)
@Measurement(iterations = 10, time = 1)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
class WriteBenchmark:

private var testData: Array[Byte] = null
private val testDataFile = Path.of("src/main/resources/CodeGeneratorRequest-pack.bin")
private var gReq: GPluginProtos.CodeGeneratorRequest = null
private var pReq: PPluginProtos.CodeGeneratorRequest = null
private var testData: Array[Byte] = null
private var gMessage: GGeneratedMessage = null
private var pMessage: PGeneratedMessage = null

private def p2g(p: PGeneratedMessage, gp: GParser[? <: GGeneratedMessage]): GGeneratedMessage = {
val out = BufferedOutput.growing()
p.writeTo(out)
out.close()
gp.parseFrom(out.toInputStream)
}

@Setup(Level.Trial)
def buildTestData(): Unit =
def buildTrialData(): Unit =
testData = Files.readAllBytes(testDataFile)
assert(testData.length == 159366, s"testData.length ${testData.length}")
gReq = GPluginProtos.CodeGeneratorRequest.parseFrom(testData)
pReq = PPluginProtos.CodeGeneratorRequest.parseFrom(BufferedInput.ofArray(testData).order(ByteOrder.LITTLE_ENDIAN))
val m = PPluginProtos.CodeGeneratorRequest.parseFrom(BufferedInput.ofArray(testData).order(ByteOrder.LITTLE_ENDIAN))
val pfs = m.getProtoFileList.asScala.toArray
for i <- 1 to 10 do
pfs.foreach(m.addProtoFile)
val out = BufferedOutput.growing()
m.writeTo(out)
out.close()
testData = out.copyToByteArray()

@Setup(Level.Invocation)
def buildInvocationData(): Unit =
gMessage = GPluginProtos.CodeGeneratorRequest.parseFrom(testData)
pMessage = PPluginProtos.CodeGeneratorRequest.parseFrom(BufferedInput.ofArray(testData).order(ByteOrder.LITTLE_ENDIAN))

private def writeGoogle(out: OutputStream): Unit =
val c = CodedOutputStream.newInstance(out, 4096)
gMessage.writeTo(c)
c.flush()
out.close()

private def writePerfio(out: BufferedOutput): Unit =
var i = 0
pMessage.writeTo(out)
out.close()

@Benchmark
def array_google(bh: Blackhole): Unit =
val baos = new ByteArrayOutputStream()
gReq.writeTo(baos)
baos.close()
writeGoogle(baos)
bh.consume(baos.size())

@Benchmark
def array_perfio(bh: Blackhole): Unit =
val bo = BufferedOutput.growing(4096)
pReq.writeTo(bo)
bo.close()
writePerfio(bo)
bh.consume(bo.length())

@Benchmark
def file_google(bh: Blackhole): Unit =
val out = new BufferedOutputStream(new FileOutputStream("/dev/null"))
gReq.writeTo(out)
writeGoogle(out)
out.close()

@Benchmark
def file_perfio(bh: Blackhole): Unit =
val bo = BufferedOutput.ofFile(Path.of("/dev/null"), 8192)
pReq.writeTo(bo)
writePerfio(bo)
bo.close()
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package perfio.proto.runtime;

import perfio.BufferedOutput;

import java.io.IOException;

public abstract class GeneratedMessage {
public abstract void writeTo(BufferedOutput out) throws IOException;
}
Loading

0 comments on commit 766463d

Please sign in to comment.