├── .gitignore ├── .ijwb └── .bazelproject ├── .travis.yml ├── BUILD ├── Cargo.Bazel.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── WORKSPACE ├── cargo-bazel-lock.json ├── nalim.bazel ├── rs ├── base64 │ ├── BUILD │ └── base64.rs ├── getloadavg │ ├── BUILD │ └── getloadavg.rs ├── hello │ ├── bin │ │ ├── BUILD │ │ └── main.rs │ └── greeter │ │ ├── BUILD │ │ └── greeter.rs └── simple_jni_lib │ ├── BUILD │ ├── README.md │ └── simplejni.rs ├── scripts ├── read-lines.sh ├── read-utf8.sh ├── run-jmh.sh └── scala-serialization.sh ├── src └── com │ └── komanov │ ├── blockingqueue │ └── jmh │ │ ├── BUILD │ │ ├── BlockingQueueBenchmark.scala │ │ └── QueueType.java │ ├── business │ ├── BUILD │ ├── example │ │ ├── BUILD │ │ ├── BusinessExample.scala │ │ └── BusinessResult.java │ └── package.scala │ ├── collection │ └── jmh │ │ ├── BUILD │ │ ├── JavaHelper.java │ │ ├── README.md │ │ └── SetMapJavaVsScalaBenchmarks.scala │ ├── compression │ ├── BUILD │ ├── BlobCompressionRatio.java │ ├── Brotli.java │ ├── CommonsCompression.java │ ├── CompressionAlgorithm.java │ ├── CompressionAlgorithms.java │ ├── DeflatePlusSize.java │ ├── Lengths.java │ ├── LocalBuffer.java │ ├── Lz4.java │ ├── README.md │ ├── SnappyCompressionAlgorithm.java │ ├── bin │ │ ├── BUILD │ │ └── CompressionLevelApp.scala │ ├── jmh │ │ ├── BUILD │ │ ├── CompressionBenchmark.scala │ │ └── InputData.java │ └── tests │ │ ├── BUILD │ │ ├── CompressionTest.scala │ │ └── DeflateWithSizeTest.scala │ ├── future │ ├── BUILD │ ├── examples │ │ ├── BUILD │ │ ├── ExceptionDemo.scala │ │ ├── Rewrite.scala │ │ └── package.scala │ ├── jmh │ │ ├── BUILD │ │ └── FutureMapBenchmark.scala │ └── package.scala │ ├── io │ ├── BUILD │ ├── package.scala │ └── tests │ │ ├── BUILD │ │ └── IoTest.scala │ ├── javaenum │ ├── BUILD │ ├── SimpleEnum.java │ └── jmh │ │ ├── BUILD │ │ └── SimpleEnumBenchmark.scala │ ├── jmh │ ├── BUILD │ └── BenchmarkBase.scala │ ├── jni │ └── rs │ │ └── bin │ │ ├── BUILD │ │ ├── Main.java │ │ ├── Native.java │ │ └── README.md │ ├── junk │ ├── exception │ │ ├── BUILD │ │ └── ExceptionCost.scala │ ├── implicitnpe │ │ ├── BUILD │ │ └── SimpleDemo.scala │ ├── mockito │ │ ├── BUILD │ │ └── MockitoAndMutableTest.scala │ ├── option_getorelse │ │ ├── BUILD │ │ └── GetOrThrowTest.scala │ ├── scala_bug_9304 │ │ ├── BUILD │ │ └── ScalaLibraryBugTest.scala │ ├── scl_8869 │ │ ├── BUILD │ │ └── WrongConvertToAnonymous.scala │ └── throttler │ │ ├── BUILD │ │ └── ThrottlerTest.scala │ ├── jwt │ └── base64 │ │ ├── BUILD │ │ ├── Base64Helper.java │ │ ├── PayloadGenerator.scala │ │ ├── bin │ │ ├── BUILD │ │ └── Main.scala │ │ ├── jmh │ │ ├── BUILD │ │ ├── Base64BenchmarkBase.scala │ │ └── Base64Benchmarks.scala │ │ ├── jni │ │ ├── BUILD │ │ ├── DirectBuffer.java │ │ ├── Native.java │ │ ├── NativeBazel.java │ │ ├── NativeCargo.java │ │ ├── NativeHelper.java │ │ ├── jmh │ │ │ ├── BUILD │ │ │ ├── Base64JniBenchmarks.scala │ │ │ ├── BazelVsCargoBenchmarks.scala │ │ │ ├── DirectBufferBenchmarks.scala │ │ │ ├── NativeLib.java │ │ │ └── README.md │ │ └── tests │ │ │ ├── BUILD │ │ │ └── NativeTest.scala │ │ └── tests │ │ ├── BUILD │ │ └── Base64HelperTest.scala │ ├── mysql │ ├── blob │ │ ├── BUILD │ │ ├── BlobGenerator.java │ │ ├── Lz4Utils.java │ │ ├── Mysql.java │ │ ├── README.md │ │ ├── bin │ │ │ ├── BUILD │ │ │ └── CompressionLevelApp.scala │ │ └── jmh │ │ │ ├── BUILD │ │ │ └── MysqlBlobBenchmarks.scala │ ├── hashset │ │ ├── BUILD │ │ ├── UuidHelper.java │ │ ├── fill │ │ │ ├── BUILD │ │ │ └── DataFiller.scala │ │ ├── perf │ │ │ ├── BUILD │ │ │ ├── ConnectionPool.java │ │ │ ├── HikariConnectionPool.java │ │ │ ├── PerfTester.java │ │ │ └── ThreadLocalConnectionPool.java │ │ └── tests │ │ │ ├── BUILD │ │ │ └── UuidHelperTest.scala │ └── streaming │ │ ├── BUILD │ │ ├── Drivers.scala │ │ ├── MysqlRunner.scala │ │ ├── Query.scala │ │ ├── README.md │ │ ├── bin │ │ ├── BUILD │ │ └── MemoryConsumptionApp.scala │ │ ├── jmh │ │ ├── BUILD │ │ ├── BenchmarkBase.scala │ │ ├── ConnectorJBenchmark.scala │ │ └── MariaDbBenchmark.scala │ │ └── tests │ │ ├── BUILD │ │ └── QueryTest.scala │ ├── nativeaccess │ ├── BUILD │ ├── BridjHelper.java │ ├── Helper.java │ ├── JavaCppHelper.java │ ├── JnaHelper.java │ ├── JniHelper.java │ ├── JnrHelper.java │ ├── JnrLibC.java │ ├── NalimHelper.java │ ├── PureJavaHelper.java │ ├── README.md │ └── jmh │ │ ├── BUILD │ │ └── NativeBenchmarks.scala │ ├── offheap │ ├── BUILD │ ├── Netty4UuidSet.java │ ├── Netty5UuidSet.java │ ├── OffHeapBasedUuidSet.java │ ├── SortedLongArrayUuidSet.java │ ├── SortedUuidArray.java │ ├── UuidSortedList.scala │ ├── alloc │ │ ├── Allocator.scala │ │ └── BUILD │ ├── jmh │ │ ├── BUILD │ │ └── Benchmarks.scala │ └── uuidhashmap │ │ ├── BUILD │ │ ├── UuidToIntHashMap.java │ │ ├── jmh │ │ ├── BUILD │ │ ├── JavaHelper.java │ │ └── MapBenchmarks.scala │ │ └── tests │ │ ├── BUILD │ │ └── UuidToIntHashMapTest.scala │ ├── readlines │ ├── BUILD │ ├── ReadLinesJavaStreams.java │ ├── ReadLinesUtils.scala │ ├── ReadUtf8Java.java │ ├── ReadUtf8Scala.scala │ ├── bin │ │ ├── BUILD │ │ └── TestApp.scala │ ├── jmh │ │ ├── BUILD │ │ ├── InputType.java │ │ ├── ReadLinesBenchmark.scala │ │ └── ReadUtf8Benchmark.scala │ └── tests │ │ ├── BUILD │ │ ├── ReadLinesUtilsTest.scala │ │ └── ReadUtf8Test.scala │ ├── redis │ ├── BUILD │ ├── README.md │ ├── StringUuidCodec.scala │ ├── bin │ │ ├── BUILD │ │ └── DataFiller.scala │ └── perf │ │ ├── BUILD │ │ └── PerfTester.java │ ├── serialization │ ├── README.md │ ├── bin │ │ ├── BUILD │ │ ├── EventsReportGenerator.scala │ │ ├── ReportGenerator.scala │ │ └── package.scala │ ├── capnproto │ │ ├── README.md │ │ ├── common.capnp │ │ ├── events.capnp │ │ ├── java.capnp │ │ └── site.capnp │ ├── converters │ │ ├── BUILD │ │ ├── BoopickleConverter.scala │ │ ├── CapnprotoConverter.scala │ │ ├── ChillConverter.scala │ │ ├── CirceConverter.scala │ │ ├── ConversionUtils.scala │ │ ├── Converters.scala │ │ ├── JavaPbConverter.scala │ │ ├── JavaSerializationConverter.scala │ │ ├── JavaThriftConverter.scala │ │ ├── JsonConverter.scala │ │ ├── JsoniterScalaConverter.scala │ │ ├── ProtobufConversionUtils.scala │ │ ├── ReflectionUtils.scala │ │ ├── ScalaPbConverter.scala │ │ ├── UpickleConverter.scala │ │ ├── api │ │ │ ├── BUILD │ │ │ ├── EventConverter.scala │ │ │ ├── MyConverter.scala │ │ │ └── SiteConverter.scala │ │ └── tests │ │ │ ├── BUILD │ │ │ ├── ConversionsUtilsTest.scala │ │ │ ├── ProtobufConversionsUtilsTest.scala │ │ │ └── SerializationTest.scala │ ├── domain │ │ ├── BUILD │ │ ├── EventProcessor.scala │ │ ├── Events.scala │ │ ├── PageComponentType.java │ │ ├── Site.scala │ │ ├── SiteFlag.java │ │ ├── SiteType.java │ │ ├── capnproto │ │ │ ├── BUILD │ │ │ ├── CommonCapnproto.java │ │ │ ├── EventsCapnproto.java │ │ │ └── SiteCapnproto.java │ │ ├── testdata │ │ │ ├── BUILD │ │ │ └── TestData.scala │ │ ├── tests │ │ │ ├── BUILD │ │ │ └── EventProcessorTest.scala │ │ └── thrift │ │ │ ├── BlogComponentDataPb.java │ │ │ ├── BlogComponentDataSetPb.java │ │ │ ├── ButtonComponentDataPb.java │ │ │ ├── ButtonComponentDataSetPb.java │ │ │ ├── DefaultMetaTagAddedPb.java │ │ │ ├── DefaultMetaTagRemovedPb.java │ │ │ ├── DomainAddedPb.java │ │ │ ├── DomainEntryPointAddedPb.java │ │ │ ├── DomainEntryPointPb.java │ │ │ ├── DomainPb.java │ │ │ ├── DomainRemovedPb.java │ │ │ ├── EntryPointPb.java │ │ │ ├── EntryPointRemovedPb.java │ │ │ ├── FreeEntryPointAddedPb.java │ │ │ ├── FreeEntryPointPb.java │ │ │ ├── MetaTagPb.java │ │ │ ├── PageAddedPb.java │ │ │ ├── PageComponentAddedPb.java │ │ │ ├── PageComponentDataPb.java │ │ │ ├── PageComponentPb.java │ │ │ ├── PageComponentPositionPb.java │ │ │ ├── PageComponentPositionResetPb.java │ │ │ ├── PageComponentPositionSetPb.java │ │ │ ├── PageComponentRemovedPb.java │ │ │ ├── PageComponentTypePb.java │ │ │ ├── PageMetaTagAddedPb.java │ │ │ ├── PageMetaTagRemovedPb.java │ │ │ ├── PageNameSetPb.java │ │ │ ├── PagePb.java │ │ │ ├── PageRemovedPb.java │ │ │ ├── PrimaryDomainSetPb.java │ │ │ ├── PrimaryEntryPointSetPb.java │ │ │ ├── SiteCreatedPb.java │ │ │ ├── SiteDescriptionSetPb.java │ │ │ ├── SiteEventDataPb.java │ │ │ ├── SiteEventPb.java │ │ │ ├── SiteFlagAddedPb.java │ │ │ ├── SiteFlagPb.java │ │ │ ├── SiteFlagRemovedPb.java │ │ │ ├── SiteNameSetPb.java │ │ │ ├── SitePb.java │ │ │ ├── SitePublishedPb.java │ │ │ ├── SiteRevisionSetPb.java │ │ │ ├── SiteTypePb.java │ │ │ ├── SiteUnpublishedPb.java │ │ │ ├── TextComponentDataPb.java │ │ │ └── TextComponentDataSetPb.java │ ├── io │ │ ├── BUILD │ │ └── IoUtils.scala │ ├── jmh │ │ ├── BUILD │ │ ├── Benchmarks.scala │ │ ├── ConverterType.java │ │ ├── InputType.java │ │ └── tests │ │ │ ├── BUILD │ │ │ └── ConverterTypeTest.scala │ └── proto │ │ ├── BUILD │ │ ├── events.proto │ │ ├── events.thrift │ │ ├── site.proto │ │ └── site.thrift │ ├── str │ └── jmh │ │ ├── BUILD │ │ ├── RegionMatchesBenchmarks.scala │ │ └── StringSplitBenchmark.scala │ ├── stringformat │ ├── BUILD │ ├── FastStringFactory.java │ ├── InputArg.java │ ├── JavaFormats.java │ ├── OptimizedConcatenation1.scala │ ├── OptimizedConcatenation2.scala │ ├── README.md │ ├── ScalaFormats.scala │ ├── bin │ │ ├── BUILD │ │ └── InputArgApp.scala │ ├── jmh │ │ ├── BUILD │ │ ├── Benchmarks.scala │ │ ├── NewStringBenchmark.scala │ │ ├── SimpleBenchmarks.scala │ │ ├── StringBuilderBenchmark.scala │ │ └── StringBuilderNewStringBenchmark.scala │ ├── macros │ │ ├── BUILD │ │ ├── MacroConcat.scala │ │ └── tests │ │ │ ├── BUILD │ │ │ └── MacrosConcatTest.scala │ └── tests │ │ ├── BUILD │ │ ├── FormatsTest.scala │ │ ├── OptimizedConcatenation1Test.scala │ │ └── OptimizedConcatenation2Test.scala │ ├── translit │ ├── BUILD │ └── RenameTranslitApp.scala │ ├── uuid │ ├── BUILD │ ├── DigitResolver.java │ ├── UuidJava0Utils.java │ ├── UuidJava1Utils.java │ ├── UuidJava2Utils.java │ ├── UuidJava3Utils.java │ ├── UuidJava4Utils.java │ ├── UuidJava5Utils.java │ ├── UuidJavaEpic2Utils.java │ ├── UuidJavaEpicUtils.java │ ├── UuidJavaFinalUtils.java │ ├── UuidScala1Utils.scala │ ├── UuidScala2Utils.scala │ ├── UuidScala3Utils.scala │ ├── UuidScala4Utils.scala │ ├── UuidScalaEpicUtils.scala │ ├── UuidScalaFinalUtils.scala │ ├── bin │ │ ├── BUILD │ │ ├── GuidParserPerformanceTest.scala │ │ └── UuidTest.java │ └── gen │ │ ├── BUILD │ │ └── UuidGen.scala │ └── ver │ ├── BUILD │ ├── ParseVersionNoAllocUtil.java │ ├── ParseVersionUtil.java │ ├── README.md │ ├── Version.scala │ ├── VersionNoAlloc.scala │ ├── jmh │ ├── BUILD │ ├── IsNumberBenchmark.scala │ ├── VersionNoAllocConvertBenchmark.scala │ ├── VersionParseBenchmark.scala │ └── VersionParseNoAllocBenchmark.scala │ └── tests │ ├── BUILD │ └── VersionTest.scala ├── third_party └── BUILD └── tools ├── BUILD ├── build_rules ├── BUILD └── prelude_bazel └── scala_specs2_junit ├── BUILD └── scala_specs2_junit.bzl /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | *.iml 3 | *.ipr 4 | .idea 5 | site 6 | .ijwb 7 | -------------------------------------------------------------------------------- /.ijwb/.bazelproject: -------------------------------------------------------------------------------- 1 | directories: 2 | rs 3 | src 4 | . 5 | 6 | #derive_targets_from_directories: true 7 | 8 | targets: 9 | //... 10 | 11 | additional_languages: 12 | scala 13 | 14 | test_sources: 15 | */tests/* 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk17 5 | 6 | dist: focal 7 | 8 | os: 9 | - linux 10 | 11 | before_install: 12 | - sudo apt install apt-transport-https curl gnupg 13 | - curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg 14 | - sudo mv bazel-archive-keyring.gpg /usr/share/keyrings 15 | - echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list 16 | - sudo apt-get -qq update 17 | - sudo apt-get install -y bazel 18 | 19 | script: 20 | - sudo bazel test -k --test_output=errors --test_tmpdir=/tmp --curses=no //... 21 | 22 | notifications: 23 | email: false 24 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkomanov/stuff/e8602ee396d2a930f22bb3e976de4f603ab36154/BUILD -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [profile.dev] 2 | opt-level = 3 3 | lto = "fat" 4 | codegen-units = 1 5 | 6 | [profile.release] 7 | opt-level = 3 8 | lto = "fat" 9 | codegen-units = 1 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dmitry Komanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stuff [![Build Status](https://app.travis-ci.com/dkomanov/stuff.svg?branch=master)](https://app.travis-ci.com/dkomanov/stuff) 2 | 3 | Contains code for different blog posts, benchmarks, test and examples of some ideas. 4 | Built with [Bazel](https://bazel.build). 5 | 6 | ## Projects 7 | 8 | * [MySQL Streaming](src/com/komanov/mysql/streaming) 9 | * [Scala Serialization](src/com/komanov/serialization) 10 | * [Scala String Interpolation Performance](src/com/komanov/stringformat) 11 | -------------------------------------------------------------------------------- /nalim.bazel: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "nalim", 3 | srcs = glob(["**/src/one/nalim/*.java"]), 4 | visibility = ["//visibility:public"], 5 | javacopts = [ 6 | "--add-modules jdk.internal.vm.ci", 7 | "--add-exports jdk.internal.vm.ci/jdk.vm.ci.code=ALL-UNNAMED", 8 | "--add-exports jdk.internal.vm.ci/jdk.vm.ci.code.site=ALL-UNNAMED", 9 | "--add-exports jdk.internal.vm.ci/jdk.vm.ci.hotspot=ALL-UNNAMED", 10 | "--add-exports jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED", 11 | "--add-exports jdk.internal.vm.ci/jdk.vm.ci.runtime=ALL-UNNAMED", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /rs/base64/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_rust//rust:defs.bzl", "rust_shared_library") 2 | 3 | rust_shared_library( 4 | name = "rust_lib", 5 | srcs = [ 6 | "base64.rs", 7 | ], 8 | rustc_flags = ["--codegen=opt-level=3"], 9 | deps = [ 10 | "@rs//:base64", 11 | "@rs//:base64-simd", 12 | "@rs//:jni", 13 | ], 14 | ) 15 | 16 | cc_binary( 17 | name = "base64_lib", 18 | linkshared = True, 19 | visibility = ["//src/com/komanov/jwt:__subpackages__"], 20 | deps = [":rust_lib"], 21 | ) 22 | -------------------------------------------------------------------------------- /rs/getloadavg/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_rust//rust:defs.bzl", "rust_shared_library") 2 | 3 | rust_shared_library( 4 | name = "rust_lib", 5 | srcs = [ 6 | "getloadavg.rs", 7 | ], 8 | rustc_flags = ["--codegen=opt-level=3"], 9 | deps = [ 10 | "@rs//:jni", 11 | "@rs//:libc", 12 | ], 13 | ) 14 | 15 | cc_binary( 16 | name = "getloadavg_lib", 17 | linkshared = True, 18 | visibility = ["//src/com/komanov/nativeaccess:__subpackages__"], 19 | deps = [":rust_lib"], 20 | ) 21 | -------------------------------------------------------------------------------- /rs/getloadavg/getloadavg.rs: -------------------------------------------------------------------------------- 1 | use jni::JNIEnv; 2 | use jni::objects::JClass; 3 | use jni::sys::jint; 4 | use libc::getloadavg; 5 | 6 | #[no_mangle] 7 | pub extern "system" fn raw_getloadavg(input: *mut f64, num: jint) -> jint { 8 | unsafe { 9 | return getloadavg(input, num); 10 | } 11 | } 12 | 13 | #[no_mangle] 14 | pub extern "system" fn Java_com_komanov_nativeaccess_JniHelper_getloadavg(_env: JNIEnv, 15 | _class: JClass, 16 | input: *mut f64, 17 | num: jint) 18 | -> jint { 19 | unsafe { 20 | return getloadavg(input, num); 21 | } 22 | } 23 | 24 | #[no_mangle] 25 | pub extern "system" fn Java_com_komanov_nativeaccess_JniHelper_foo(_env: JNIEnv, 26 | _class: JClass, 27 | num: jint) 28 | -> jint { 29 | return num; 30 | } 31 | -------------------------------------------------------------------------------- /rs/hello/bin/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_rust//rust:defs.bzl", "rust_binary") 2 | 3 | rust_binary( 4 | name = "bin", 5 | srcs = ["main.rs"], 6 | deps = ["//rs/hello/greeter"], 7 | ) 8 | -------------------------------------------------------------------------------- /rs/hello/bin/main.rs: -------------------------------------------------------------------------------- 1 | extern crate greeter; 2 | 3 | fn main() { 4 | let hello = greeter::Greeter::new("Hello"); 5 | hello.greet("world"); 6 | } 7 | -------------------------------------------------------------------------------- /rs/hello/greeter/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@rules_rust//rust:defs.bzl", "rust_library") 4 | 5 | rust_library( 6 | name = "greeter", 7 | srcs = [ 8 | "greeter.rs", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /rs/hello/greeter/greeter.rs: -------------------------------------------------------------------------------- 1 | pub struct Greeter { 2 | greeting: String, 3 | } 4 | 5 | impl Greeter { 6 | pub fn new(greeting: &str) -> Greeter { 7 | Greeter { greeting: greeting.to_string(), } 8 | } 9 | 10 | pub fn greet(&self, thing: &str) { 11 | println!("{} {}", &self.greeting, thing); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /rs/simple_jni_lib/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_rust//rust:defs.bzl", "rust_shared_library") 2 | 3 | rust_shared_library( 4 | name = "rust_lib", 5 | srcs = [ 6 | "simplejni.rs", 7 | ], 8 | deps = [ 9 | "@rs//:jni", 10 | ], 11 | ) 12 | 13 | # This is a workaround. `rust_shared_library` doesn't work by itself, need to wrap in something that does work: 14 | cc_binary( 15 | name = "simple_jni_lib", 16 | linkshared = True, 17 | visibility = ["//src/com/komanov/jni/rs/bin:__pkg__"], 18 | deps = [":rust_lib"], 19 | ) 20 | -------------------------------------------------------------------------------- /rs/simple_jni_lib/README.md: -------------------------------------------------------------------------------- 1 | ## How to JNI with Rust and Bazel 2 | 3 | * https://docs.rs/jni/latest/jni/ 4 | * [Bazel: Java app with JNI dependency](https://stackoverflow.com/questions/46256118/bazel-java-app-with-jni-dependency) and related [answer](https://github.com/hlopko/bazel-jni-example) is useful to understand how it works in bazel for C++. But requirement for `jvm_flags = ["-Djava.library.path=cpp"]` is redundant (you probably don't want to pass it everywhere). 5 | 6 | As a result answered [here](https://stackoverflow.com/questions/68896878/how-to-make-rust-jni-bindings-with-bazel/73829909#73829909). 7 | 8 | * https://docs.oracle.com/en/java/javase/11/docs/specs/jni/index.html 9 | * https://developer.android.com/training/articles/perf-jni#java 10 | -------------------------------------------------------------------------------- /rs/simple_jni_lib/simplejni.rs: -------------------------------------------------------------------------------- 1 | use jni::JNIEnv; 2 | // These objects are what you should use as arguments to your native 3 | // function. They carry extra lifetime information to prevent them escaping 4 | // this context and getting used after being GC'd. 5 | use jni::objects::{JClass, JString}; 6 | // This is just a pointer. We'll be returning it from our function. We 7 | // can't return one of the objects with lifetime information because the 8 | // lifetime checker won't let us. 9 | use jni::sys::jstring; 10 | 11 | #[no_mangle] 12 | pub extern "system" fn Java_com_komanov_jni_rs_bin_Native_greet(env: JNIEnv, 13 | _class: JClass, 14 | input: JString) 15 | -> jstring { 16 | // First, we have to get the string out of Java. Check out the `strings` 17 | // module for more info on how this works. 18 | let input: String = env.get_string(input).expect("Couldn't get java string!").into(); 19 | 20 | // Then we have to create a new Java string to return. Again, more info 21 | // in the `strings` module. 22 | let output = env.new_string(format!("Hello, {}!", input)) 23 | .expect("Couldn't create java string!"); 24 | 25 | // Finally, extract the raw pointer to return. 26 | output.into_inner() 27 | } 28 | -------------------------------------------------------------------------------- /scripts/read-lines.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | TS=`date --iso-8601` 4 | TARGET_DIR=src/com/komanov/readlines 5 | TARGET_NAME=jmh 6 | DATA_DIR=./site/public/data/read-lines 7 | DRIVE_TYPE="$1" 8 | 9 | if [ ${DRIVE_TYPE} = "" ] 10 | then 11 | echo "Expected drive type: ssd|hdd" 12 | exit 1 13 | fi 14 | 15 | JSON_PATH="${DATA_DIR}/jmh_${TS}_${DRIVE_TYPE}.json" 16 | LOG_PATH="${DATA_DIR}/jmh_${TS}_${DRIVE_TYPE}.log" 17 | 18 | bazel run //${TARGET_DIR}/${TARGET_NAME} -- -rf json -rff jmh.json ReadLinesBenchmark | tee jmh.log 19 | mv ./bazel-bin/${TARGET_DIR}/${TARGET_NAME}/${TARGET_NAME}.runfiles/stuff/jmh.json ${JSON_PATH} 20 | mv ./jmh.log ${LOG_PATH} 21 | 22 | git add ${JSON_PATH} ${LOG_PATH} 23 | 24 | echo ${TS} 25 | echo "Don't forget to change JS, commit, push and deploy!" 26 | -------------------------------------------------------------------------------- /scripts/read-utf8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | TS=`date --iso-8601` 4 | TARGET_DIR=src/com/komanov/readlines 5 | TARGET_NAME=jmh 6 | DATA_DIR=./site/public/data/read-utf8 7 | 8 | JSON_PATH="${DATA_DIR}/jmh_${TS}.json" 9 | LOG_PATH="${DATA_DIR}/jmh_${TS}.log" 10 | 11 | bazel run //${TARGET_DIR}/${TARGET_NAME} -- -rf json -rff jmh.json ReadUtf8Benchmark | tee jmh.log 12 | mv ./bazel-bin/${TARGET_DIR}/${TARGET_NAME}/${TARGET_NAME}.runfiles/stuff/jmh.json ${JSON_PATH} 13 | mv ./jmh.log ${LOG_PATH} 14 | 15 | git add ${JSON_PATH} ${LOG_PATH} 16 | 17 | echo ${TS} 18 | echo "Don't forget to change JS, commit, push and deploy!" 19 | -------------------------------------------------------------------------------- /scripts/run-jmh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | TARGET=$1 7 | NAME=$2 8 | 9 | if [ "${TARGET}" = "" ] || [ "${NAME}" = "" ] 10 | then 11 | echo "Usage: $0 //src/com/komanov/target/jmh:jmh my-benchmark" 12 | exit 1 13 | fi 14 | 15 | SCRIPT=$(realpath "$0") 16 | SCRIPT_DIR=$(dirname "$SCRIPT") 17 | ROOT_DIR=$(realpath "$SCRIPT_DIR"/..) 18 | SITE_DIR=$(realpath "$ROOT_DIR"/../dkomanov.github.io) 19 | DATA_DIR=$SITE_DIR/static/data/charts/$NAME 20 | 21 | mkdir -p "$DATA_DIR" 22 | cd "$ROOT_DIR" 23 | 24 | LABEL="${TARGET}_deploy.jar" 25 | bazel build "$LABEL" 26 | JAR_FILE=$(bazel cquery --output starlark --starlark:expr '"\n".join([f.path for f in target.files.to_list()])' "$LABEL") 27 | 28 | [ "${DISABLE_JDK8}" != "1" ] && /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -jar "$JAR_FILE" -jvmArgsAppend "$JDK_ARGS_APPEND $JDK8_ARGS_APPEND" $JDK_JMH_EXTRA $JDK8_JMH_EXTRA -rf json -rff "$DATA_DIR"/jdk8.json | stdbuf -oL sed "s#${SITE_DIR}##g" | tee "$DATA_DIR"/jdk8.log.txt 29 | [ "${DISABLE_JDK11}" != "1" ] && /usr/lib/jvm/java-11-openjdk-amd64/bin/java -jar "$JAR_FILE" -jvmArgsAppend "$JDK_ARGS_APPEND $JDK11_ARGS_APPEND" $JDK_JMH_EXTRA $JDK11_JMH_EXTRA -rf json -rff "$DATA_DIR"/jdk11.json | stdbuf -oL sed "s#${SITE_DIR}##g" | tee "$DATA_DIR"/jdk11.log.txt 30 | [ "${DISABLE_JDK17}" != "1" ] && /usr/lib/jvm/java-17-openjdk-amd64/bin/java -jar "$JAR_FILE" -jvmArgsAppend "$JDK_ARGS_APPEND $JDK17_ARGS_APPEND" $JDK_JMH_EXTRA $JDK17_JMH_EXTRA -rf json -rff "$DATA_DIR"/jdk17.json | stdbuf -oL sed "s#${SITE_DIR}##g" | tee "$DATA_DIR"/jdk17.log.txt 31 | -------------------------------------------------------------------------------- /scripts/scala-serialization.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | thrift -r --gen java -out src src/com/komanov/serialization/proto/events.thrift 7 | thrift -r --gen java -out src src/com/komanov/serialization/proto/site.thrift 8 | -------------------------------------------------------------------------------- /src/com/komanov/blockingqueue/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "enums", 6 | ], 7 | ) 8 | 9 | java_library( 10 | name = "enums", 11 | srcs = glob(["*.java"]), 12 | visibility = ["//src/com/komanov/blockingqueue/jmh:__subpackages__"], 13 | ) 14 | -------------------------------------------------------------------------------- /src/com/komanov/blockingqueue/jmh/QueueType.java: -------------------------------------------------------------------------------- 1 | package com.komanov.blockingqueue.jmh; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | import java.util.concurrent.LinkedBlockingDeque; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | 8 | public enum QueueType { 9 | ArrayBlockingQueue { 10 | @Override 11 | public BlockingQueue create(int capacity) { 12 | return new ArrayBlockingQueue<>(capacity); 13 | } 14 | }, 15 | FairArrayBlockingQueue { 16 | @Override 17 | public BlockingQueue create(int capacity) { 18 | return new ArrayBlockingQueue<>(capacity, true); 19 | } 20 | }, 21 | LinkedBlockingDeque { 22 | @Override 23 | public BlockingQueue create(int capacity) { 24 | return new LinkedBlockingDeque<>(capacity); 25 | } 26 | }, 27 | LinkedBlockingQueue { 28 | @Override 29 | public BlockingQueue create(int capacity) { 30 | return new LinkedBlockingQueue<>(capacity); 31 | } 32 | }, 33 | ; 34 | 35 | public abstract BlockingQueue create(int capacity); 36 | } 37 | -------------------------------------------------------------------------------- /src/com/komanov/business/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "business", 3 | srcs = glob(["*.scala"]), 4 | visibility = ["//visibility:public"], 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/business/example/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "example", 3 | srcs = [ 4 | "BusinessExample.scala", 5 | "BusinessResult.java", 6 | ], 7 | deps = [ 8 | "//src/com/komanov/business", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /src/com/komanov/business/example/BusinessResult.java: -------------------------------------------------------------------------------- 1 | package com.komanov.business.example; 2 | 3 | public enum BusinessResult 4 | { 5 | Ok, UserNotFound, RequestNotFound, NotOwner, 6 | } 7 | -------------------------------------------------------------------------------- /src/com/komanov/business/package.scala: -------------------------------------------------------------------------------- 1 | package com.komanov 2 | 3 | import scala.language.implicitConversions 4 | import scala.util.Try 5 | 6 | package object business { 7 | 8 | implicit final class ModelOrResultFromOption[M](private val opt: Option[M]) 9 | extends AnyVal { 10 | // converts Option to Either.RightProjection 11 | def orResult[R](result: => R) = 12 | opt.fold[Either[R, M]](Left(result))(Right(_)).right 13 | } 14 | 15 | implicit final class ModelOrResultFromTry[M](private val opt: Try[M]) 16 | extends AnyVal { 17 | // converts Try to Either.RightProjection 18 | def orResult[R](result: => R) = 19 | (if (opt.isFailure) Left(result) else Right(opt.get)).right 20 | } 21 | 22 | implicit def toResult[R](e: Either[R, R]): R = 23 | e.fold(identity, identity) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/com/komanov/collection/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | ":helper", 6 | ], 7 | ) 8 | 9 | java_library( 10 | name = "helper", 11 | srcs = ["JavaHelper.java"], 12 | deps = [ 13 | "@io_bazel_rules_scala_scala_library", 14 | ], 15 | visibility = ["//visibility:public"], 16 | ) 17 | -------------------------------------------------------------------------------- /src/com/komanov/collection/jmh/JavaHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.collection.jmh; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import java.util.UUID; 7 | 8 | public class JavaHelper { 9 | public static int run(Set collection, List toCheck) { 10 | int r = 0; 11 | for (UUID uuid : toCheck) { 12 | if (collection.contains(uuid)) { 13 | r += 1; 14 | } 15 | } 16 | return r; 17 | } 18 | 19 | public static int run(scala.collection.immutable.Set collection, List toCheck) { 20 | int r = 0; 21 | for (UUID uuid : toCheck) { 22 | if (collection.contains(uuid)) { 23 | r += 1; 24 | } 25 | } 26 | return r; 27 | } 28 | 29 | public static int run(scala.collection.mutable.Set collection, List toCheck) { 30 | int r = 0; 31 | for (UUID uuid : toCheck) { 32 | if (collection.contains(uuid)) { 33 | r += 1; 34 | } 35 | } 36 | return r; 37 | } 38 | 39 | public static int run(Map collection, List toCheck) { 40 | int r = 0; 41 | for (UUID uuid : toCheck) { 42 | if (collection.containsKey(uuid)) { 43 | r += 1; 44 | } 45 | } 46 | return r; 47 | } 48 | 49 | public static int run(scala.collection.immutable.Map collection, List toCheck) { 50 | int r = 0; 51 | for (UUID uuid : toCheck) { 52 | if (collection.contains(uuid)) { 53 | r += 1; 54 | } 55 | } 56 | return r; 57 | } 58 | 59 | public static int run(scala.collection.mutable.Map collection, List toCheck) { 60 | int r = 0; 61 | for (UUID uuid : toCheck) { 62 | if (collection.contains(uuid)) { 63 | r += 1; 64 | } 65 | } 66 | return r; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/com/komanov/collection/jmh/README.md: -------------------------------------------------------------------------------- 1 | ## Set/Map performance: Java vs Scala 2 | 3 | ``` 4 | scripts/run-jmh.sh //src/com/komanov/collection/jmh:jmh set-map-java-vs-scala-2-12 5 | scripts/run-jmh.sh //src/com/komanov/collection/jmh:jmh set-map-java-vs-scala-2-13 6 | ``` -------------------------------------------------------------------------------- /src/com/komanov/compression/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "compression", 3 | srcs = glob(["*.java"]), 4 | visibility = ["//visibility:public"], 5 | runtime_deps = [ 6 | "@compression//:com_aayushatharva_brotli4j_native_linux_x86_64", 7 | "@compression//:com_github_luben_zstd_jni", 8 | ], 9 | deps = [ 10 | "@compression//:com_aayushatharva_brotli4j_brotli4j", 11 | "@compression//:org_apache_commons_commons_compress", 12 | "@compression//:org_apache_commons_commons_lang3", 13 | "@compression//:org_lz4_lz4_java", 14 | "@compression//:org_xerial_snappy_snappy_java", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /src/com/komanov/compression/BlobCompressionRatio.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Random; 5 | 6 | public enum BlobCompressionRatio { 7 | LOW_COMPRESSION_1_3 { 8 | public byte[] generateBlob(int length) { 9 | return generateImpl(COMP_1_3, length); 10 | } 11 | }, 12 | 13 | MEDIUM_COMPRESSION_2_1 { 14 | public byte[] generateBlob(int length) { 15 | return generateImpl(COMP_2_1, length); 16 | } 17 | }, 18 | 19 | HIGH_COMPRESSION_3_4 { 20 | public byte[] generateBlob(int length) { 21 | return generateImpl(COMP_3_4, length); 22 | } 23 | }, 24 | 25 | EXTRA_HIGH_COMPRESSION_6_2 { 26 | public byte[] generateBlob(int length) { 27 | return generateImpl(COMP_6_2, length); 28 | } 29 | }, 30 | ; 31 | 32 | public abstract byte[] generateBlob(int length); 33 | 34 | private static final Random rnd = new Random(); 35 | private static final byte[] COMP_6_2 = "01".getBytes(StandardCharsets.US_ASCII); 36 | private static final byte[] COMP_3_4 = "0123".getBytes(StandardCharsets.US_ASCII); 37 | private static final byte[] COMP_2_1 = "0123456789".getBytes(StandardCharsets.US_ASCII); 38 | private static final byte[] COMP_1_3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".getBytes(StandardCharsets.US_ASCII); 39 | 40 | private static byte[] generateImpl(byte[] alphabet, int length) { 41 | byte[] result = new byte[length]; 42 | for (int i = 0; i < result.length; i++) { 43 | result[i] = alphabet[rnd.nextInt(alphabet.length)]; 44 | } 45 | return result; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/com/komanov/compression/Brotli.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | import com.aayushatharva.brotli4j.Brotli4jLoader; 4 | import com.aayushatharva.brotli4j.decoder.Decoder; 5 | import com.aayushatharva.brotli4j.decoder.DecoderJNI; 6 | import com.aayushatharva.brotli4j.decoder.DirectDecompress; 7 | import com.aayushatharva.brotli4j.encoder.Encoder; 8 | 9 | public class Brotli implements CompressionAlgorithm { 10 | static { 11 | Brotli4jLoader.ensureAvailability(); 12 | } 13 | 14 | private final Encoder.Parameters parameters; 15 | 16 | public static Brotli BR_0 = new Brotli(new Encoder.Parameters().setQuality(0)); 17 | public static Brotli BR_6 = new Brotli(new Encoder.Parameters().setQuality(6)); 18 | public static Brotli BR_11 = new Brotli(new Encoder.Parameters().setQuality(11)); 19 | 20 | private Brotli(Encoder.Parameters parameters) { 21 | this.parameters = parameters; 22 | } 23 | 24 | public byte[] encode(byte[] data) throws Throwable { 25 | return com.aayushatharva.brotli4j.encoder.Encoder.compress(data, parameters); 26 | } 27 | 28 | public byte[] decode(byte[] encoded) throws Throwable { 29 | DirectDecompress result = Decoder.decompress(encoded); 30 | assert result.getResultStatus() == DecoderJNI.Status.DONE; 31 | return result.getDecompressedData(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/komanov/compression/CommonsCompression.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | import org.apache.commons.compress.compressors.CompressorInputStream; 4 | import org.apache.commons.compress.compressors.CompressorOutputStream; 5 | import org.apache.commons.compress.compressors.CompressorStreamFactory; 6 | import org.apache.commons.compress.utils.IOUtils; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | 11 | public class CommonsCompression implements CompressionAlgorithm { 12 | private final String algorithm; 13 | 14 | private CommonsCompression(String algorithm) { 15 | this.algorithm = algorithm; 16 | } 17 | 18 | public static final CommonsCompression GZIP = new CommonsCompression(CompressorStreamFactory.GZIP); 19 | public static final CommonsCompression DEFLATE = new CommonsCompression(CompressorStreamFactory.DEFLATE); 20 | public static final CommonsCompression ZSTANDARD = new CommonsCompression(CompressorStreamFactory.ZSTANDARD); 21 | // It doesn't work! decode on large inputs (64K+) may just return empty array!!! 22 | public static final CommonsCompression SNAPPY = new CommonsCompression(CompressorStreamFactory.SNAPPY_FRAMED); 23 | 24 | @Override 25 | public byte[] encode(byte[] data) throws Throwable { 26 | ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length / 2); 27 | try (CompressorOutputStream compressor = CompressorStreamFactory.getSingleton().createCompressorOutputStream(algorithm, baos)) { 28 | compressor.write(data); 29 | compressor.close(); 30 | return baos.toByteArray(); 31 | } 32 | } 33 | 34 | @Override 35 | public byte[] decode(byte[] encoded) throws Throwable { 36 | try (CompressorInputStream s = CompressorStreamFactory.getSingleton().createCompressorInputStream(algorithm, new ByteArrayInputStream(encoded))) { 37 | return IOUtils.toByteArray(s); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/komanov/compression/CompressionAlgorithm.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | public interface CompressionAlgorithm { 4 | byte[] encode(byte[] data) throws Throwable; 5 | 6 | byte[] decode(byte[] encoded) throws Throwable; 7 | } 8 | -------------------------------------------------------------------------------- /src/com/komanov/compression/CompressionAlgorithms.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | public enum CompressionAlgorithms { 4 | gzip(CommonsCompression.GZIP), 5 | deflate(CommonsCompression.DEFLATE), 6 | 7 | // MySQL COMPRESS/UNCOMPRESS style: 4 bytes of uncompressed size + deflated. 8 | deflateWithSize(DeflatePlusSize.INSTANCE) { 9 | public boolean isOptimization() { 10 | return true; 11 | } 12 | }, 13 | 14 | zstd(CommonsCompression.ZSTANDARD), 15 | snappy(SnappyCompressionAlgorithm.INSTANCE), 16 | 17 | // https://github.com/hyperxpro/Brotli4j 18 | brotli_0(Brotli.BR_0), 19 | brotli_6(Brotli.BR_6), 20 | brotli_11(Brotli.BR_11), 21 | 22 | // https://github.com/lz4/lz4-java 23 | lz4_fast(Lz4.FAST), 24 | lz4_high9(Lz4.HIGH_9), 25 | lz4_high17(Lz4.HIGH_17), 26 | ; 27 | 28 | public final CompressionAlgorithm algorithm; 29 | 30 | CompressionAlgorithms(CompressionAlgorithm algorithm) { 31 | this.algorithm = algorithm; 32 | } 33 | 34 | public byte[] encode(byte[] data) throws Throwable { 35 | return algorithm.encode(data); 36 | } 37 | 38 | public byte[] decode(byte[] encoded) throws Throwable { 39 | return algorithm.decode(encoded); 40 | } 41 | 42 | public boolean isOptimization() { 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/komanov/compression/Lengths.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | public abstract class Lengths { 4 | private Lengths() { 5 | } 6 | 7 | public static final int[] JavaCompressionLengths = new int[]{ 8 | 1024, 9 | 2048, 10 | 3072, 11 | 4096, 12 | 5120, 13 | 6144, 14 | 7168, 15 | 8192, 16 | 9216, 17 | 10240, 18 | 20480, 19 | 30720, 20 | 40960, 21 | 51200, 22 | 61440, 23 | 71680, 24 | 81920, 25 | 92160, 26 | 102400, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/com/komanov/compression/LocalBuffer.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | public abstract class LocalBuffer { 4 | private LocalBuffer() { 5 | } 6 | 7 | private static final ThreadLocal tl = new ThreadLocal<>(); 8 | 9 | public static byte[] getBuffer(int size) { 10 | byte[] bytes = tl.get(); 11 | if (bytes == null || bytes.length < size) { 12 | bytes = new byte[size]; 13 | tl.set(bytes); 14 | } 15 | return bytes; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/komanov/compression/Lz4.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | import net.jpountz.lz4.LZ4Compressor; 4 | import net.jpountz.lz4.LZ4Factory; 5 | import net.jpountz.lz4.LZ4FastDecompressor; 6 | 7 | import java.util.Arrays; 8 | 9 | import static org.apache.commons.lang3.Conversion.byteArrayToInt; 10 | import static org.apache.commons.lang3.Conversion.intToByteArray; 11 | 12 | class Lz4 implements CompressionAlgorithm { 13 | private static final LZ4FastDecompressor decompressor = LZ4Factory.nativeInstance().fastDecompressor(); 14 | 15 | public static Lz4 FAST = new Lz4(LZ4Factory.nativeInstance().fastCompressor()); 16 | public static Lz4 HIGH_9 = new Lz4(LZ4Factory.nativeInstance().highCompressor()); 17 | public static Lz4 HIGH_17 = new Lz4(LZ4Factory.nativeInstance().highCompressor(17)); 18 | 19 | private final LZ4Compressor compressor; 20 | 21 | private Lz4(LZ4Compressor compressor) { 22 | this.compressor = compressor; 23 | } 24 | 25 | @Override 26 | public byte[] encode(byte[] data) { 27 | byte[] out = LocalBuffer.getBuffer(compressor.maxCompressedLength(data.length) + 4); 28 | intToByteArray(data.length, 0, out, 0, 4); 29 | int written = compressor.compress(data, 0, data.length, out, 4); 30 | return Arrays.copyOf(out, written + 4); 31 | } 32 | 33 | @Override 34 | public byte[] decode(byte[] encoded) throws Throwable { 35 | int size = byteArrayToInt(encoded, 0, 0, 0, 4); 36 | byte[] out = new byte[size]; 37 | int read = decompressor.decompress(encoded, 4, out, 0, size); 38 | 39 | assert read == encoded.length - 4; 40 | 41 | return out; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/com/komanov/compression/README.md: -------------------------------------------------------------------------------- 1 | ### Java Compression 2 | 3 | ```bash 4 | scripts/run-jmh.sh //src/com/komanov/compression/jmh:jmh java-compression 5 | ``` 6 | -------------------------------------------------------------------------------- /src/com/komanov/compression/SnappyCompressionAlgorithm.java: -------------------------------------------------------------------------------- 1 | package com.komanov.compression; 2 | 3 | import org.xerial.snappy.Snappy; 4 | 5 | public class SnappyCompressionAlgorithm implements CompressionAlgorithm { 6 | private SnappyCompressionAlgorithm() { 7 | } 8 | 9 | public static final SnappyCompressionAlgorithm INSTANCE = new SnappyCompressionAlgorithm(); 10 | 11 | @Override 12 | public byte[] encode(byte[] data) throws Throwable { 13 | return Snappy.compress(data); 14 | } 15 | 16 | @Override 17 | public byte[] decode(byte[] encoded) throws Throwable { 18 | return Snappy.uncompress(encoded); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/komanov/compression/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "bin", 3 | srcs = ["CompressionLevelApp.scala"], 4 | main_class = "com.komanov.compression.bin.CompressionLevelApp", 5 | deps = [ 6 | "//src/com/komanov/compression", 7 | "//src/com/komanov/compression/jmh:input", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/compression/bin/CompressionLevelApp.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.compression.bin 2 | 3 | import com.komanov.compression.jmh.InputData 4 | import com.komanov.compression.{BlobCompressionRatio, CompressionAlgorithms, Lengths} 5 | 6 | import java.nio.file.Files 7 | 8 | object CompressionLevelApp extends App { 9 | for (sz <- Lengths.JavaCompressionLengths) { 10 | println(s"\"$sz\",") 11 | } 12 | println() 13 | println() 14 | 15 | val list = CompressionAlgorithms.values().filter(!_.isOptimization) 16 | BlobCompressionRatio.values.foreach { ratio => 17 | println() 18 | println(ratio) 19 | println(s"Length\t${list.mkString("\t")}") 20 | Lengths.JavaCompressionLengths.foreach { len => 21 | val blob = ratio.generateBlob(len) 22 | println(s"$len\t${list.map(_.encode(blob).length).mkString("\t")}") 23 | } 24 | } 25 | 26 | println() 27 | println(s"Input\tLength\t${list.mkString("\t")}") 28 | InputData.values().map(input => input -> Files.readAllBytes(input.path)).sortBy(_._2.length).foreach { case (input, blob) => 29 | println(s"$input\t${blob.length}\t${list.map(_.encode(blob).length).mkString("\t")}") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/komanov/compression/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = ["CompressionBenchmark.scala"], 4 | deps = [ 5 | ":input", 6 | "//src/com/komanov/compression", 7 | ], 8 | ) 9 | 10 | java_library( 11 | name = "input", 12 | srcs = ["InputData.java"], 13 | visibility = ["//visibility:public"], 14 | ) 15 | -------------------------------------------------------------------------------- /src/com/komanov/compression/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/compression", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/compression/tests/CompressionTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.compression.tests 2 | 3 | import com.komanov.compression.CompressionAlgorithms 4 | import org.specs2.mutable.SpecificationWithJUnit 5 | import org.specs2.specification.core.Fragments 6 | 7 | import java.util 8 | import scala.collection.immutable.ArraySeq 9 | import scala.util.Random 10 | 11 | class CompressionTest extends SpecificationWithJUnit { 12 | 13 | sequential 14 | 15 | "encode/decode" >> { 16 | Fragments.foreach(for { 17 | method <- ArraySeq.unsafeWrapArray(CompressionAlgorithms.values()) 18 | data <- Seq( 19 | "".getBytes(), 20 | "a".getBytes(), 21 | Random.alphanumeric.take(100).mkString.getBytes, 22 | Random.alphanumeric.take(128 * 1024).mkString.getBytes, 23 | randomBytes(1000), 24 | randomBytes(128 * 1024), 25 | ) 26 | } yield (method, data)) { case (method, data) => 27 | s"work 2 way for ${data.length} bytes [${method.name}]" >> { 28 | val encoded = method.encode(util.Arrays.copyOf(data, data.length)) 29 | if (data.length > 0) { 30 | encoded mustNotEqual data 31 | } 32 | method.decode(encoded) mustEqual data 33 | } 34 | } 35 | } 36 | 37 | private def randomBytes(len: Int): Array[Byte] = { 38 | val bytes = new Array[Byte](len) 39 | Random.nextBytes(bytes) 40 | bytes 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/komanov/compression/tests/DeflateWithSizeTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.compression.tests 2 | 3 | import com.komanov.compression.{CompressionAlgorithms, DeflatePlusSize} 4 | import org.specs2.mutable.SpecificationWithJUnit 5 | 6 | class DeflateWithSizeTest extends SpecificationWithJUnit { 7 | "deflate with size" >> { 8 | "return empty array for empty input" >> { 9 | val emptyArray = new Array[Byte](0) 10 | DeflatePlusSize.INSTANCE.encode(emptyArray) mustEqual emptyArray 11 | DeflatePlusSize.INSTANCE.decode(emptyArray) mustEqual emptyArray 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/com/komanov/future/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "future", 3 | srcs = glob([ 4 | "*.scala", 5 | ]), 6 | visibility = ["//visibility:public"], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/future/examples/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "examples", 3 | srcs = glob([ 4 | "*.scala", 5 | ]), 6 | deps = [ 7 | "//src/com/komanov/future", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/future/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/future", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/io/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "io", 3 | srcs = glob([ 4 | "*.scala", 5 | "*.java", 6 | ]), 7 | visibility = [ 8 | "//src/com/komanov/io/tests:__pkg__", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /src/com/komanov/io/package.scala: -------------------------------------------------------------------------------- 1 | package com.komanov 2 | 3 | import scala.util.control.NonFatal 4 | 5 | package object io { 6 | 7 | def withResources[T <: AutoCloseable, V](r: => T)(f: T => V): V = { 8 | val resource: T = r 9 | require(resource != null, "resource is null") 10 | var exception: Throwable = null 11 | try { 12 | f(resource) 13 | } catch { 14 | case e: Throwable => 15 | exception = e 16 | throw e 17 | } finally { 18 | closeAndAddSuppressed(exception, resource) 19 | } 20 | } 21 | 22 | private def closeAndAddSuppressed(e: Throwable, resource: AutoCloseable): Unit = { 23 | if (e != null) { 24 | try { 25 | resource.close() 26 | } catch { 27 | case NonFatal(suppressed) => 28 | e.addSuppressed(suppressed) 29 | case fatal: Throwable if NonFatal(e) => 30 | fatal.addSuppressed(e) 31 | throw fatal 32 | case fatal: InterruptedException => 33 | fatal.addSuppressed(e) 34 | throw fatal 35 | case fatal: Throwable => 36 | e.addSuppressed(fatal) 37 | } 38 | } else { 39 | resource.close() 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/com/komanov/io/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/io", 6 | "@maven//:org_mockito_mockito_core", 7 | "@maven//:org_specs2_specs2_mock_2_13", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/io/tests/IoTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.io.tests 2 | 3 | import com.komanov.io._ 4 | import org.specs2.mock.Mockito 5 | import org.specs2.mutable.SpecWithJUnit 6 | 7 | class IoTest extends SpecWithJUnit with Mockito { 8 | 9 | "withResources" should { 10 | "succeed" >> { 11 | val resource = mock[AutoCloseable] 12 | 13 | withResources(resource)(_ => "shalom") mustEqual "shalom" 14 | 15 | got { 16 | one(resource).close() 17 | } 18 | } 19 | 20 | "close resource if function threw" >> { 21 | val resource = mock[AutoCloseable] 22 | val ex = new IllegalStateException("Boom!") 23 | 24 | withResources[AutoCloseable, String](resource)(_ => throw ex) must throwA[IllegalStateException](ex) 25 | 26 | got { 27 | one(resource).close() 28 | } 29 | } 30 | 31 | "add suppressed exception if function threw and close threw" >> { 32 | val resource = mock[AutoCloseable] 33 | val ex = new IllegalStateException("Boom!") 34 | val suppressed = new RuntimeException("Failed to close!") 35 | 36 | resource.close() throws suppressed 37 | 38 | withResources[AutoCloseable, String](resource)(_ => throw ex) must throwA[IllegalStateException].like { 39 | case e => 40 | e.getSuppressed.toSeq must contain(exactly(suppressed.asInstanceOf[Throwable])) 41 | e mustEqual ex 42 | } 43 | 44 | got { 45 | one(resource).close() 46 | } 47 | } 48 | 49 | "throw exception thrown on close" >> { 50 | val resource = mock[AutoCloseable] 51 | val ex = new RuntimeException("Failed to close!") 52 | 53 | resource.close() throws ex 54 | 55 | withResources(resource)(_ => "shalom") must throwA[RuntimeException](ex) 56 | 57 | got { 58 | one(resource).close() 59 | } 60 | } 61 | 62 | "throw if resource is null" >> { 63 | withResources[AutoCloseable, String](null)(_ => "shalom") must throwA[IllegalArgumentException] 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/com/komanov/javaenum/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "javaenum", 3 | srcs = glob(["*.java"]), 4 | visibility = [ 5 | "//src/com/komanov/javaenum/jmh:__pkg__", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/javaenum/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/javaenum", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | visibility = ["//visibility:public"], 5 | exports = [ 6 | "@io_bazel_rules_scala_org_openjdk_jmh_jmh_core", 7 | ], 8 | deps = [ 9 | "@io_bazel_rules_scala_org_openjdk_jmh_jmh_core", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/komanov/jmh/BenchmarkBase.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.jmh 2 | 3 | import org.openjdk.jmh.annotations._ 4 | 5 | import java.util.concurrent.TimeUnit 6 | 7 | @State(Scope.Benchmark) 8 | @BenchmarkMode(Array(Mode.AverageTime)) 9 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 10 | @Fork(value = 1, jvmArgs = Array("-Xmx2G")) 11 | @Threads(2) 12 | @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 13 | @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) 14 | abstract class BenchmarkBase 15 | -------------------------------------------------------------------------------- /src/com/komanov/jni/rs/bin/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "native", 3 | srcs = ["Native.java"], 4 | deps = [ 5 | "//rs/simple_jni_lib", 6 | ], 7 | ) 8 | 9 | java_binary( 10 | name = "bin", 11 | srcs = ["Main.java"], 12 | main_class = "com.komanov.jni.rs.bin.Main", 13 | deps = [ 14 | ":native", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /src/com/komanov/jni/rs/bin/Main.java: -------------------------------------------------------------------------------- 1 | package com.komanov.jni.rs.bin; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | String greeting = Native.greet(args.length == 1 ? args[0] : "Stranger"); 6 | System.out.println(greeting); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/com/komanov/jni/rs/bin/Native.java: -------------------------------------------------------------------------------- 1 | package com.komanov.jni.rs.bin; 2 | 3 | public abstract class Native { 4 | private Native() { 5 | } 6 | 7 | static { 8 | System.loadLibrary("simple_jni_lib"); 9 | } 10 | 11 | public static native String greet(String name); 12 | } 13 | -------------------------------------------------------------------------------- /src/com/komanov/jni/rs/bin/README.md: -------------------------------------------------------------------------------- 1 | To generate header file: 2 | ``` 3 | javac -h . Native.java 4 | ``` 5 | 6 | Output: 7 | ```cpp 8 | /* DO NOT EDIT THIS FILE - it is machine generated */ 9 | #include 10 | /* Header for class com_komanov_jni_rs_bin_Native */ 11 | 12 | #ifndef _Included_com_komanov_jni_rs_bin_Native 13 | #define _Included_com_komanov_jni_rs_bin_Native 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | /* 18 | * Class: com_komanov_jni_rs_bin_Native 19 | * Method: greet 20 | * Signature: (Ljava/lang/String;)Ljava/lang/String; 21 | */ 22 | JNIEXPORT jstring JNICALL Java_com_komanov_jni_rs_bin_Native_greet 23 | (JNIEnv *, jclass, jstring); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | #endif 29 | ``` 30 | -------------------------------------------------------------------------------- /src/com/komanov/junk/exception/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "exception", 3 | srcs = ["ExceptionCost.scala"], 4 | main_class = "com.komanov.junk.exception.ExceptionCost", 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/junk/exception/ExceptionCost.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.junk.exception 2 | 3 | /* 4 | the latest run: 5 | 6 | convert avg 2 ns, total 24027983 7 | ctor avg 23 ns, total 233135146 8 | create avg 1456 ns, total 14560671210 9 | */ 10 | object ExceptionCost extends App { 11 | 12 | private val N = 10000000 13 | 14 | type ConvertFunc = () => Unit 15 | 16 | val scalaTry = scala.util.Failure(new RuntimeException) 17 | 18 | val convertF: ConvertFunc = () => convertException() 19 | val ctorF: ConvertFunc = () => constructorException() 20 | val createF: ConvertFunc = () => createException() 21 | 22 | val algorithms = Map( 23 | "convert " -> convertF, 24 | "ctor " -> ctorF, 25 | "create " -> createF 26 | ) 27 | 28 | doWarmUp() 29 | for ((name, f) <- algorithms) { 30 | doTest(name, f) 31 | } 32 | 33 | private def doTest(name: String, f: ConvertFunc): Unit = { 34 | val start = System.nanoTime() 35 | for (_ <- 0 until N) { 36 | f() 37 | } 38 | val duration = System.nanoTime() - start 39 | val avg = duration / N 40 | println(s"$name avg $avg ns, total $duration") 41 | } 42 | 43 | private def doWarmUp() = { 44 | // warm up 45 | for (i <- 0 until (N / 10)) { 46 | for (a <- algorithms) { 47 | a._2() 48 | } 49 | } 50 | } 51 | 52 | private def convertException(): Unit = { 53 | if (scalaTry.isFailure) scala.util.Failure(scalaTry.exception) else scala.util.Success(scalaTry.get) 54 | } 55 | 56 | private def constructorException(): Unit = { 57 | scala.util.Try(scalaTry.get) 58 | } 59 | 60 | private def createException(): Unit = { 61 | new RuntimeException 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/com/komanov/junk/implicitnpe/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "implicitnpe", 3 | srcs = glob(["*.scala"]), 4 | main_class = "com.komanov.junk.implicitnpe.SimpleDemo", 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/junk/implicitnpe/SimpleDemo.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.junk.implicitnpe 2 | 3 | import com.komanov.junk.implicitnpe.anotherpackage.MyContext 4 | import com.komanov.junk.implicitnpe.anotherpackage.builders.aMyContext 5 | 6 | // https://stackoverflow.com/questions/61917884/nullpointerexception-on-implicit-resolution 7 | case class Context(id: String) 8 | object Context { 9 | implicit def `ContextHolder to Context`(implicit holder: ContextHolder): Context = holder.context 10 | } 11 | trait ContextHolder { 12 | def context: Context 13 | } 14 | 15 | object anotherpackage { 16 | case class MyContext(name: String, context: Context) extends ContextHolder 17 | 18 | object builders { 19 | def aMyContext(name: String)(implicit context: Context = Context("test")): MyContext = 20 | MyContext(name, context) 21 | } 22 | } 23 | 24 | object SimpleDemo extends App { 25 | implicit val myContext: MyContext = { 26 | val myContext = null 27 | aMyContext("name") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/komanov/junk/mockito/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "mockito", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "@maven//:org_mockito_mockito_core", 6 | "@maven//:org_specs2_specs2_mock_2_13", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /src/com/komanov/junk/option_getorelse/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "option_getorelse", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "@maven//:org_specs2_specs2_mock_2_13", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/junk/option_getorelse/GetOrThrowTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.junk.option_getorelse 2 | 3 | import org.specs2.mutable.SpecificationWithJUnit 4 | 5 | class GetOrThrowTest extends SpecificationWithJUnit { 6 | "Option.getOrElse" should { 7 | "throw on None" in { 8 | val o = Option[String](null) 9 | o.getOrElse(throw new MyException(true)) must throwA[MyException] 10 | } 11 | 12 | "not throw on Some" in { 13 | val o = Option("1") 14 | o.getOrElse(throw new MyException(false)) === "1" 15 | } 16 | } 17 | 18 | class MyException(shouldBeThrow: Boolean) extends Throwable { 19 | require(shouldBeThrow) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/com/komanov/junk/scala_bug_9304/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | ) 5 | -------------------------------------------------------------------------------- /src/com/komanov/junk/scl_8869/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "scl_8869", 3 | srcs = ["WrongConvertToAnonymous.scala"], 4 | ) 5 | -------------------------------------------------------------------------------- /src/com/komanov/junk/scl_8869/WrongConvertToAnonymous.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.junk.scl_8869 2 | 3 | // https://youtrack.jetbrains.com/issue/SCL-8869 4 | class WrongConvertToAnonymous extends MockitoStubsCut { 5 | val myInterface: MyInterface = null 6 | 7 | myInterface.convert(1) answers (o => o match { 8 | case t: Int => t.toString 9 | }) 10 | 11 | myInterface.convert(1) answers (_ match { 12 | case t: Int => t.toString 13 | }) 14 | 15 | } 16 | 17 | trait MyInterface { 18 | def convert(i: Int): String 19 | } 20 | 21 | trait MockitoStubsCut { 22 | implicit def theStubbed[T](c: => T): Stubbed[T] = new Stubbed(c) 23 | 24 | class Stubbed[T](c: => T) { 25 | def answers(function: Any => T) = { 26 | } 27 | 28 | def answers(function: (Any, Any) => T) = { 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/komanov/junk/throttler/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "throttler", 3 | srcs = glob(["*.scala"]), 4 | ) 5 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "base64", 3 | srcs = ["Base64Helper.java"], 4 | visibility = ["//src/com/komanov/jwt/base64:__subpackages__"], 5 | deps = [ 6 | "@jwt//:commons_codec_commons_codec", 7 | ], 8 | ) 9 | 10 | scala_library( 11 | name = "generator", 12 | srcs = ["PayloadGenerator.scala"], 13 | visibility = ["//src/com/komanov/jwt/base64:__subpackages__"], 14 | ) 15 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/Base64Helper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64; 2 | 3 | import static org.apache.commons.codec.binary.BaseNCodec.MIME_CHUNK_SIZE; 4 | 5 | public abstract class Base64Helper { 6 | private Base64Helper() { 7 | } 8 | 9 | public static final class Jdk { 10 | private Jdk() { 11 | } 12 | 13 | private static final java.util.Base64.Decoder regularDecoder = java.util.Base64.getDecoder(); 14 | private static final java.util.Base64.Decoder urlSafeDecoder = java.util.Base64.getUrlDecoder(); 15 | 16 | private static final java.util.Base64.Encoder regularEncoder = java.util.Base64.getEncoder(); 17 | private static final java.util.Base64.Encoder urlSafeEncoder = java.util.Base64.getUrlEncoder().withoutPadding(); 18 | 19 | public static byte[] encode(byte[] data) { 20 | return regularEncoder.encode(data); 21 | } 22 | 23 | public static byte[] encodeUrlSafe(byte[] data) { 24 | return urlSafeEncoder.encode(data); 25 | } 26 | 27 | public static byte[] decode(byte[] data) { 28 | return regularDecoder.decode(data); 29 | } 30 | 31 | public static byte[] decodeUrlSafe(byte[] data) { 32 | return urlSafeDecoder.decode(data); 33 | } 34 | } 35 | 36 | public static class Commons { 37 | private Commons() { 38 | } 39 | 40 | private static final org.apache.commons.codec.binary.Base64 regular = new org.apache.commons.codec.binary.Base64(MIME_CHUNK_SIZE, null, false); 41 | private static final org.apache.commons.codec.binary.Base64 urlSafe = new org.apache.commons.codec.binary.Base64(MIME_CHUNK_SIZE, null, true); 42 | 43 | public static byte[] encode(byte[] data) { 44 | return regular.encode(data); 45 | } 46 | 47 | public static byte[] encodeUrlSafe(byte[] data) { 48 | return urlSafe.encode(data); 49 | } 50 | 51 | public static byte[] decode(byte[] data) { 52 | return regular.decode(data); 53 | } 54 | 55 | public static byte[] decodeUrlSafe(byte[] data) { 56 | return urlSafe.decode(data); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/PayloadGenerator.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64 2 | 3 | import scala.util.Random 4 | 5 | object PayloadGenerator { 6 | def apply(length: Int): String = 7 | Random.alphanumeric.take(length).mkString 8 | } 9 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "bin", 3 | srcs = ["Main.scala"], 4 | main_class = "com.komanov.jwt.base64.bin.Main", 5 | deps = [ 6 | "//src/com/komanov/jwt/base64", 7 | "//src/com/komanov/jwt/base64:generator", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/bin/Main.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64.bin 2 | 3 | import com.komanov.jwt.base64.PayloadGenerator 4 | 5 | object Main extends App { 6 | printOfLength(1) 7 | printOfLength(10) 8 | printOfLength(50) 9 | printOfLength(100) 10 | printOfLength(500) 11 | printOfLength(1000) 12 | printOfLength(10000) 13 | 14 | 15 | def printOfLength(len: Int): Unit = { 16 | val s = PayloadGenerator(len) 17 | println(s"$len -> \"$s\",") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = ["Base64Benchmarks.scala"], 4 | deps = [ 5 | ":jmh_base", 6 | "//src/com/komanov/jwt/base64", 7 | ], 8 | ) 9 | 10 | scala_library( 11 | name = "jmh_base", 12 | srcs = ["Base64BenchmarkBase.scala"], 13 | visibility = ["//src/com/komanov/jwt/base64:__subpackages__"], 14 | exports = [ 15 | "//src/com/komanov/jmh", 16 | ], 17 | deps = [ 18 | "//src/com/komanov/jmh", 19 | "//src/com/komanov/jwt/base64", 20 | "//src/com/komanov/jwt/base64:generator", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "jni", 3 | srcs = glob(["*.java"]), 4 | visibility = [ 5 | "//src/com/komanov/jwt:__subpackages__", 6 | ], 7 | deps = [ 8 | "//rs/base64:base64_lib", 9 | "//src/com/komanov/offheap/alloc", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/DirectBuffer.java: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64.jni; 2 | 3 | import com.komanov.offheap.alloc.Allocator; 4 | 5 | public final class DirectBuffer { 6 | private static final ThreadLocal inputBuffer = new ThreadLocal<>(); 7 | private static final ThreadLocal outputBuffer = new ThreadLocal<>(); 8 | 9 | private final long address; 10 | private final int bufferSize; 11 | private volatile boolean released; 12 | 13 | private DirectBuffer(long address, int bufferSize) { 14 | this.address = address; 15 | this.bufferSize = bufferSize; 16 | } 17 | 18 | @Override 19 | protected void finalize() { 20 | release(); 21 | } 22 | 23 | public static long getInputBuffer(int size) { 24 | return getDirectBuffer(inputBuffer, size); 25 | } 26 | 27 | public static long getOutputBuffer(int size) { 28 | return getDirectBuffer(outputBuffer, size); 29 | } 30 | 31 | private static long getDirectBuffer(ThreadLocal tl, int size) { 32 | DirectBuffer db = tl.get(); 33 | if (db == null || db.bufferSize < size) { 34 | if (db != null) { 35 | db.release(); 36 | } 37 | db = new DirectBuffer(Allocator.alloc(size), size); 38 | tl.set(db); 39 | } 40 | return db.address; 41 | } 42 | 43 | private void release() { 44 | if (!released) { 45 | Allocator.release(address); 46 | released = true; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/Native.java: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64.jni; 2 | 3 | public interface Native { 4 | // base64::encode_config 5 | 6 | byte[] encodeConfigUrlSafe(byte[] payload); 7 | 8 | // base64::decode_config 9 | 10 | byte[] decodeConfigUrlSafe1(byte[] encoded); 11 | 12 | // get_byte_array_elements instead of convert_byte_array 13 | byte[] decodeConfigUrlSafe2(byte[] encoded); 14 | 15 | // pass size instead of JNI get array length 16 | byte[] decodeConfigUrlSafe3(byte[] encoded, int size); 17 | 18 | // get_primitive_array_critical instead of get_byte_array_elements 19 | byte[] decodeConfigUrlSafe4(byte[] encoded, int size); 20 | 21 | // base64::decode_config_slice 22 | 23 | byte[] decodeConfigSliceUrlSafe1(byte[] encoded, int size); 24 | 25 | byte[] decodeConfigSliceUrlSafe2NoCache(byte[] encoded); 26 | 27 | byte[] decodeConfigSliceUrlSafe2Cache(byte[] encoded); 28 | 29 | byte[] decodeConfigSliceUrlSafe3CacheInputOutput(byte[] encoded); 30 | 31 | // base64::encode_config_slice 32 | 33 | byte[] encodeConfigSlice1NoCache(byte[] payload); 34 | 35 | byte[] encodeConfigSlice1Cache(byte[] encoded); 36 | 37 | byte[] encodeConfigSlice2CacheInputOutput(byte[] encoded); 38 | 39 | // base64-simd 40 | 41 | byte[] encodeSimd(byte[] payload); 42 | 43 | byte[] decodeSimd(byte[] encoded); 44 | 45 | byte[] decodeSimdInPlace(byte[] encoded); 46 | } 47 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/NativeHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64.jni; 2 | 3 | import com.komanov.offheap.alloc.Allocator; 4 | import sun.misc.Unsafe; 5 | 6 | public abstract class NativeHelper { 7 | private NativeHelper() { 8 | } 9 | 10 | public static byte[] toByteArray(long address, int length) { 11 | byte[] result = new byte[length]; 12 | Allocator.unsafe().copyMemory(null, address, result, Unsafe.ARRAY_BYTE_BASE_OFFSET, length); 13 | return result; 14 | } 15 | 16 | public static void copyFromByteArray(long address, byte[] input, int inputSize) { 17 | Allocator.unsafe().copyMemory(input, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, address, inputSize); 18 | } 19 | 20 | public static int sizeForEncode(int inputSize) { 21 | return inputSize * 4 / 3 + 4; 22 | } 23 | 24 | public static int sizeForDecode(int inputSize) { 25 | // No need to add 4, because it's without padding! 26 | return inputSize * 3 / 4; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = ["Base64JniBenchmarks.scala"], 4 | deps = [ 5 | "//src/com/komanov/jwt/base64", 6 | "//src/com/komanov/jwt/base64/jmh:jmh_base", 7 | "//src/com/komanov/jwt/base64/jni", 8 | ], 9 | ) 10 | 11 | scala_benchmark_jmh( 12 | name = "bazel_vs_cargo", 13 | srcs = ["BazelVsCargoBenchmarks.scala"], 14 | deps = [ 15 | ":native_lib", 16 | "//src/com/komanov/jwt/base64", 17 | "//src/com/komanov/jwt/base64/jmh:jmh_base", 18 | "//src/com/komanov/jwt/base64/jni", 19 | ], 20 | ) 21 | 22 | scala_benchmark_jmh( 23 | name = "direct_buffer", 24 | srcs = ["DirectBufferBenchmarks.scala"], 25 | deps = [ 26 | "//src/com/komanov/jwt/base64/jmh:jmh_base", 27 | "//src/com/komanov/jwt/base64/jni", 28 | ], 29 | ) 30 | 31 | java_library( 32 | name = "native_lib", 33 | srcs = ["NativeLib.java"], 34 | deps = ["//src/com/komanov/jwt/base64/jni"], 35 | ) 36 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/jmh/NativeLib.java: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64.jni.jmh; 2 | 3 | import com.komanov.jwt.base64.jni.Native; 4 | import com.komanov.jwt.base64.jni.NativeBazel; 5 | import com.komanov.jwt.base64.jni.NativeCargo; 6 | 7 | public enum NativeLib { 8 | BAZEL { 9 | @Override 10 | public Native getNative() { 11 | return NativeBazel.INSTANCE; 12 | } 13 | }, 14 | CARGO { 15 | @Override 16 | public Native getNative() { 17 | return NativeCargo.INSTANCE; 18 | } 19 | }, 20 | ; 21 | 22 | public abstract Native getNative(); 23 | } 24 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/jmh/README.md: -------------------------------------------------------------------------------- 1 | ## JNI benchmark 2 | 3 | To run this benchmark and have results for `base64-simd`, you need to build [rust-stuff](https://github.com/dkomanov/rust-stuff) repo with `cargo build -r` and then set proper path for `-Djava.library.path` (assuming rust-stuff is in the same parent directory as stuff, we can use `$PWD`): 4 | 5 | ```bash 6 | bazel run //src/com/komanov/jwt/base64/jni/jmh -- -jvmArgsAppend "-Djava.library.path=bazel-bin/rs/base64:rs/base64:$PWD/../rust-stuff/target/release" 7 | ``` 8 | 9 | What's the problem with `base64-simd`? I can't make it work properly while building it with bazel. To ensure that bazel sucks, there is a benchmark: 10 | 11 | ```bash 12 | bazel run //src/com/komanov/jwt/base64/jni/jmh:bazel_vs_cargo -- -jvmArgsAppend "-Djava.library.path=bazel-bin/rs/base64:rs/base64:$PWD/../rust-stuff/target/release" 13 | ``` 14 | 15 | To no wait for comparison forever: 16 | ```bash 17 | bazel run //src/com/komanov/jwt/base64/jni/jmh:bazel_vs_cargo -- -jvmArgsAppend "-Djava.library.path=bazel-bin/rs/base64:rs/base64:$PWD/../rust-stuff/target/release" -p length=10000 -p dataset=fixed 18 | ``` 19 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | jvm_flags = [ 5 | "-Djava.library.path=rs/base64", 6 | ], 7 | deps = [ 8 | "//src/com/komanov/jwt/base64/jni", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/jni/tests/NativeTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64.jni.tests 2 | 3 | import com.komanov.jwt.base64.jni 4 | import org.specs2.mutable.SpecWithJUnit 5 | 6 | class NativeBazelTest extends NativeTestBase(jni.NativeBazel.INSTANCE) 7 | 8 | // To do actual test we need to put proper path to library.path 9 | class NativeCargoTest extends NativeTestBase(jni.NativeCargo.INSTANCE) 10 | 11 | abstract class NativeTestBase(lib: jni.Native) extends SpecWithJUnit { 12 | 13 | private val encoded = "YWJj".getBytes() 14 | private val decoded = "abc".getBytes() 15 | 16 | "encode" >> { 17 | "succeed encodeConfigUrlSafe" >> { 18 | lib.encodeConfigUrlSafe(decoded) mustEqual encoded 19 | } 20 | "succeed encodeConfigSlice1NoCache" >> { 21 | lib.encodeConfigSlice1NoCache(decoded) mustEqual encoded 22 | } 23 | "succeed encodeConfigSlice1Cache" >> { 24 | lib.encodeConfigSlice1Cache(decoded) mustEqual encoded 25 | } 26 | "succeed encodeConfigSlice2CacheInputOutput" >> { 27 | lib.encodeConfigSlice1Cache(decoded) mustEqual encoded 28 | } 29 | "succeed encodeSimd" >> { 30 | lib.encodeSimd(decoded) mustEqual encoded 31 | } 32 | } 33 | 34 | "decode" >> { 35 | "succeed decodeConfigUrlSafe1" >> { 36 | lib.decodeConfigUrlSafe1(encoded) mustEqual decoded 37 | } 38 | "succeed decodeConfigUrlSafe2" >> { 39 | lib.decodeConfigUrlSafe2(encoded) mustEqual decoded 40 | } 41 | "succeed decodeConfigUrlSafe3" >> { 42 | lib.decodeConfigUrlSafe3(encoded, encoded.length) mustEqual decoded 43 | } 44 | "succeed decodeConfigUrlSafe4" >> { 45 | lib.decodeConfigUrlSafe4(encoded, encoded.length) mustEqual decoded 46 | } 47 | "succeed decodeConfigSliceUrlSafe1" >> { 48 | lib.decodeConfigSliceUrlSafe1(encoded, encoded.length) mustEqual decoded 49 | } 50 | "succeed decodeConfigSliceUrlSafe2NoCache" >> { 51 | lib.decodeConfigSliceUrlSafe2NoCache(encoded) mustEqual decoded 52 | } 53 | "succeed decodeConfigSliceUrlSafe2Cache" >> { 54 | lib.decodeConfigSliceUrlSafe2Cache(encoded) mustEqual decoded 55 | } 56 | "succeed decodeConfigSliceUrlSafe3CacheInputOutput" >> { 57 | lib.decodeConfigSliceUrlSafe3CacheInputOutput(encoded) mustEqual decoded 58 | } 59 | "succeed decodeSimd" >> { 60 | lib.decodeSimd(encoded) mustEqual decoded 61 | } 62 | "succeed decodeSimdInPlace" >> { 63 | lib.decodeSimdInPlace(encoded) mustEqual decoded 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/jwt/base64", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/jwt/base64/tests/Base64HelperTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.jwt.base64.tests 2 | 3 | import com.komanov.jwt.base64.Base64Helper.{Commons, Jdk} 4 | import org.specs2.mutable.SpecificationWithJUnit 5 | import org.specs2.specification.core.Fragments 6 | 7 | import java.nio.charset.StandardCharsets 8 | 9 | class Base64HelperTest extends SpecificationWithJUnit { 10 | Fragments.foreach[(String, String)](Seq( 11 | "" -> "", 12 | "a" -> "YQ==", 13 | "abcd" -> "YWJjZA==", 14 | "nmmфывыаывпврапопррло" -> "bm1t0YTRi9Cy0YvQsNGL0LLQv9Cy0YDQsNC/0L7Qv9GA0YDQu9C+", 15 | )) { case (data, expected) => 16 | s"(jdk) regular encode-decode for input: $data" >> { 17 | val encoded = Jdk.encode(data.getBytes(StandardCharsets.UTF_8)) 18 | str(encoded) mustEqual expected 19 | val bytes = Jdk.decode(encoded) 20 | str(bytes) mustEqual data 21 | } 22 | 23 | s"(commons) regular encode-decode for input: $data" >> { 24 | val encoded = Commons.encode(data.getBytes(StandardCharsets.UTF_8)) 25 | str(encoded) mustEqual expected 26 | val bytes = Commons.decode(encoded) 27 | str(bytes) mustEqual data 28 | } 29 | } 30 | 31 | Fragments.foreach[(String, String)](Seq( 32 | "" -> "", 33 | "a" -> "YQ", 34 | "abcd" -> "YWJjZA", 35 | "nmmфывыаывпврапопррло" -> "bm1t0YTRi9Cy0YvQsNGL0LLQv9Cy0YDQsNC_0L7Qv9GA0YDQu9C-", 36 | )) { case (data, expected) => 37 | s"(jdk) regular encode-decode for input: $data" >> { 38 | val encoded = Jdk.encodeUrlSafe(data.getBytes(StandardCharsets.UTF_8)) 39 | str(encoded) mustEqual expected 40 | val bytes = Jdk.decodeUrlSafe(encoded) 41 | str(bytes) mustEqual data 42 | } 43 | 44 | s"(commons) regular encode-decode for input: $data" >> { 45 | val encoded = Commons.encodeUrlSafe(data.getBytes(StandardCharsets.UTF_8)) 46 | str(encoded) mustEqual expected 47 | val bytes = Commons.decodeUrlSafe(encoded) 48 | str(bytes) mustEqual data 49 | } 50 | } 51 | 52 | private def str(bytes: Array[Byte]): String = 53 | new String(bytes, StandardCharsets.UTF_8) 54 | } 55 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "blob", 3 | srcs = glob(["*.java"]), 4 | visibility = ["//visibility:public"], 5 | deps = [ 6 | "//src/com/komanov/compression", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/BlobGenerator.java: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.blob; 2 | 3 | import java.util.SortedSet; 4 | import java.util.TreeSet; 5 | 6 | public class BlobGenerator { 7 | public static int[] Lengths = allLengths(); 8 | 9 | private static int[] allLengths() { 10 | SortedSet list = new TreeSet<>(); 11 | list.add(1024); 12 | for (int kb = 5; kb < 100; kb += 5) { 13 | list.add(kb * 1024); 14 | } 15 | 16 | for (int mb = 0; mb <= 4; ++mb) { 17 | for (int kb = 0; kb <= 9; ++kb) { 18 | int len = mb * 1024 * 1024 + kb * 100 * 1024; 19 | if (len != 0) { 20 | list.add(len); 21 | } 22 | } 23 | } 24 | list.add(5 * 1024 * 1024); 25 | 26 | return list.stream().mapToInt(v -> v).toArray(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/Lz4Utils.java: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.blob; 2 | 3 | import com.komanov.compression.CompressionAlgorithms; 4 | 5 | public class Lz4Utils { 6 | public static byte[] compress(byte[] arr) { 7 | try { 8 | return CompressionAlgorithms.lz4_high9.encode(arr); 9 | } catch (Throwable e) { 10 | throw new RuntimeException(e); 11 | } 12 | } 13 | 14 | public static byte[] decompress(byte[] arr) { 15 | try { 16 | return CompressionAlgorithms.lz4_high9.decode(arr); 17 | } catch (Throwable e) { 18 | throw new RuntimeException(e); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/Mysql.java: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.blob; 2 | 3 | import com.komanov.compression.DeflatePlusSize; 4 | 5 | import java.util.zip.DataFormatException; 6 | 7 | public class Mysql { 8 | public static String URL = "jdbc:mysql://localhost:3306/blob_db?cacheServerConfiguration=true&createDatabaseIfNotExist=false"; 9 | public static String USER = "debian-sys-maint"; 10 | public static String PASSWORD = ""; 11 | 12 | public static byte[] mysqlCompress(byte[] arr) { 13 | try { 14 | return DeflatePlusSize.INSTANCE.encode(arr); 15 | } catch (Throwable e) { 16 | throw new RuntimeException(e); 17 | } 18 | } 19 | 20 | public static byte[] mysqlDecompress(byte[] arr) throws DataFormatException { 21 | try { 22 | return DeflatePlusSize.INSTANCE.decode(arr); 23 | } catch (Throwable e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/README.md: -------------------------------------------------------------------------------- 1 | ### MySQL Blob Throughput 2 | 3 | Tests/benchmarks for getting large blobs from MySQL. 4 | 5 | #### Install MySQL 6 | 7 | ```bash 8 | sudo apt install mysql-server-8.0 9 | sudo cat /etc/mysql/debian.cnf # take user/password from it 10 | ``` 11 | 12 | #### Init Schema 13 | 14 | ```sql 15 | CREATE DATABASE blob_db; 16 | USE blob_db; 17 | 18 | CREATE TABLE uncompressed_blobs ( 19 | id INT NOT NULL PRIMARY KEY, 20 | data MEDIUMBLOB NOT NULL 21 | ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; 22 | 23 | CREATE TABLE compressed_blobs ( 24 | id INT NOT NULL PRIMARY KEY, 25 | data MEDIUMBLOB NOT NULL 26 | ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED; 27 | ``` 28 | 29 | * https://dev.mysql.com/doc/refman/8.0/en/innodb-row-format.html 30 | 31 | #### Run Benchmark 32 | 33 | * Comment out jdk8/jdk11 (irrelevant for the benchmark) 34 | * `scripts/run-jmh.sh //src/com/komanov/mysql/blob/jmh:jmh mysql-blob-fetch` 35 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "bin", 3 | srcs = ["CompressionLevelApp.scala"], 4 | main_class = "com.komanov.mysql.blob.bin.CompressionLevelApp", 5 | deps = [ 6 | "//src/com/komanov/compression", 7 | "//src/com/komanov/mysql/blob", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/bin/CompressionLevelApp.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.blob.bin 2 | 3 | import com.komanov.compression.BlobCompressionRatio 4 | import com.komanov.mysql.blob.{BlobGenerator, Lz4Utils, Mysql} 5 | 6 | import scala.jdk.CollectionConverters._ 7 | 8 | object CompressionLevelApp extends App { 9 | def measured[T](f: => T): (T, Long) = { 10 | val start = System.nanoTime() 11 | val r = f 12 | val duration = System.nanoTime() - start 13 | (r, duration / 1000) 14 | } 15 | 16 | for (sz <- BlobGenerator.Lengths) { 17 | println(s"\"$sz\",") 18 | } 19 | 20 | println() 21 | println(s"Length\t${BlobCompressionRatio.values.mkString("\t\t\t\t\t\t")}") 22 | println(s"\t${BlobCompressionRatio.values.map(_ => "zlib size\tzlib ratio\tzlib duration\tlz4 size\tlz4 ratio\tlz4 duration").mkString("\t")}") 23 | for (sz <- BlobGenerator.Lengths) { 24 | val rr = BlobCompressionRatio.values.map { ratio => 25 | val blob = ratio.generateBlob(sz) 26 | val (zlibed, zlibDuration) = measured(Mysql.mysqlCompress(blob)) 27 | val (lz4ed, lz4Duration) = measured(Lz4Utils.compress(blob)) 28 | 29 | def of(compressed: Array[Byte], duration: Long) = { 30 | Seq( 31 | compressed.length, 32 | String.format("%.2f", blob.length.toDouble / compressed.length.toDouble), 33 | duration, 34 | ).mkString("\t") 35 | } 36 | 37 | of(zlibed, zlibDuration) + "\t" + of(lz4ed, lz4Duration) 38 | } 39 | 40 | println(s"$sz\t${rr.mkString("\t")}") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/blob/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = ["MysqlBlobBenchmarks.scala"], 4 | deps = [ 5 | "//src/com/komanov/compression", 6 | "//src/com/komanov/compression/jmh:input", 7 | "//src/com/komanov/mysql/blob", 8 | "@mysql_blob_maven//:mysql_mysql_connector_java", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "hashset", 3 | srcs = ["UuidHelper.java"], 4 | visibility = ["//visibility:public"], 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/UuidHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.hashset; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.UUID; 5 | 6 | public abstract class UuidHelper { 7 | private UuidHelper() { 8 | } 9 | 10 | public static byte[] toBytes(UUID value) { 11 | byte[] array = new byte[16]; 12 | ByteBuffer bb = ByteBuffer.wrap(array); 13 | bb.putLong(value.getMostSignificantBits()); 14 | bb.putLong(value.getLeastSignificantBits()); 15 | return array; 16 | } 17 | 18 | public static UUID fromBytes(byte[] array) { 19 | ByteBuffer bb = ByteBuffer.wrap(array); 20 | return new UUID(bb.getLong(), bb.getLong()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/fill/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "fill", 3 | srcs = ["DataFiller.scala"], 4 | main_class = "com.komanov.mysql.hashset.fill.DataFiller", 5 | deps = [ 6 | "//src/com/komanov/mysql/hashset", 7 | "@mysql_hashset_maven//:mysql_mysql_connector_java", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/fill/DataFiller.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.hashset.fill 2 | 3 | import com.komanov.mysql.hashset.UuidHelper 4 | 5 | import java.nio.file.{Files, Paths} 6 | import java.sql.DriverManager 7 | import java.util.UUID 8 | import scala.collection.mutable 9 | 10 | // Usage: USER PASSWORD SET_NAME INPUT_FILE 11 | object DataFiller extends App { 12 | 13 | val Array(user, password, setName, input) = args 14 | val tableName = setName.replace("-", "") 15 | 16 | // CREATE DATABASE hash_set; 17 | // CREATE TABLE set1m (id BINARY(16) NOT NULL PRIMARY KEY) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; 18 | // CREATE TABLE set10m (id BINARY(16) NOT NULL PRIMARY KEY) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; 19 | // CREATE TABLE set100k (id BINARY(16) NOT NULL PRIMARY KEY) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; 20 | 21 | val inputPath = Paths.get(input) 22 | require(Files.exists(inputPath), s"INPUT_FILE doesn't exist: $input") 23 | 24 | val url = s"jdbc:mysql://localhost:3306/hash_set?cacheServerConfiguration=true&createDatabaseIfNotExist=false&rewriteBatchedStatements=true" 25 | 26 | val chunk = mutable.ListBuffer[UUID]() 27 | 28 | def dumpChunk(): Unit = { 29 | val conn = DriverManager.getConnection(url, user, password) 30 | val st = conn.prepareStatement(s"INSERT INTO $tableName (id) VALUES (?)") 31 | chunk.foreach { uuid => 32 | st.setBytes(1, UuidHelper.toBytes(uuid)) 33 | st.addBatch() 34 | st.clearParameters() 35 | } 36 | st.executeBatch() 37 | st.close() 38 | conn.close() 39 | chunk.clear() 40 | } 41 | 42 | Files.lines(inputPath).forEach { line => 43 | chunk += UUID.fromString(line) 44 | if (chunk.size == 50000) { 45 | dumpChunk() 46 | } 47 | } 48 | if (chunk.nonEmpty) { 49 | dumpChunk() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/perf/BUILD: -------------------------------------------------------------------------------- 1 | java_binary( 2 | name = "perf", 3 | srcs = glob(["*.java"]), 4 | jvm_flags = ["-Xmx2G"], 5 | main_class = "com.komanov.mysql.hashset.perf.PerfTester", 6 | deps = [ 7 | "//src/com/komanov/mysql/hashset", 8 | "@mysql_hashset_maven//:com_zaxxer_HikariCP", 9 | "@mysql_hashset_maven//:mysql_mysql_connector_java", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/perf/ConnectionPool.java: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.hashset.perf; 2 | 3 | import java.sql.Connection; 4 | 5 | interface ConnectionPool { 6 | Connection acquire() throws Throwable; 7 | 8 | void release(Connection conn) throws Throwable; 9 | 10 | void shutdown() throws Throwable; 11 | } 12 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/perf/HikariConnectionPool.java: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.hashset.perf; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | 6 | import java.sql.Connection; 7 | 8 | class HikariConnectionPool implements ConnectionPool { 9 | private final HikariDataSource ds; 10 | 11 | HikariConnectionPool(String url) { 12 | HikariConfig config = new HikariConfig(); 13 | config.setJdbcUrl(url); 14 | config.setMaximumPoolSize(PerfTester.MaxParallelism); 15 | config.setMinimumIdle(PerfTester.MaxParallelism); 16 | this.ds = new HikariDataSource(config); 17 | } 18 | 19 | @Override 20 | public Connection acquire() throws Throwable { 21 | return ds.getConnection(); 22 | } 23 | 24 | @Override 25 | public void release(Connection conn) throws Throwable { 26 | conn.close(); 27 | } 28 | 29 | @Override 30 | public void shutdown() throws Throwable { 31 | ds.close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/perf/ThreadLocalConnectionPool.java: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.hashset.perf; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | class ThreadLocalConnectionPool implements ConnectionPool { 9 | private static final ThreadLocal tl = new ThreadLocal<>(); 10 | private final String url; 11 | private final List connections = new ArrayList<>(); 12 | 13 | public ThreadLocalConnectionPool(String url) { 14 | this.url = url; 15 | } 16 | 17 | @Override 18 | public Connection acquire() throws Throwable { 19 | Connection conn = tl.get(); 20 | if (conn == null) { 21 | conn = DriverManager.getConnection(url); 22 | connections.add(conn); 23 | tl.set(conn); 24 | } 25 | return conn; 26 | } 27 | 28 | @Override 29 | public void release(Connection conn) throws Throwable { 30 | // do nothing, we clean up only on shutdown. 31 | } 32 | 33 | @Override 34 | public void shutdown() throws Throwable { 35 | for (Connection conn : connections) { 36 | conn.close(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/mysql/hashset", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/hashset/tests/UuidHelperTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.hashset.tests 2 | 3 | import com.komanov.mysql.hashset.UuidHelper 4 | import org.specs2.mutable.SpecWithJUnit 5 | 6 | import java.util.UUID 7 | 8 | class UuidHelperTest extends SpecWithJUnit { 9 | "UuidHelper" should { 10 | "convert 2-ways" >> { 11 | val uuid = UUID.randomUUID 12 | UuidHelper.fromBytes(UuidHelper.toBytes(uuid)) mustEqual uuid 13 | } 14 | 15 | "have correct binary representation" >> { 16 | val uuid = UUID.fromString("524ab4a5-a1e8-47bf-8220-6a58569c0a62") 17 | val array = UuidHelper.toBytes(uuid) 18 | array mustEqual Array[Byte]( 19 | 0x52.toByte, 20 | 0x4a.toByte, 21 | 0xb4.toByte, 22 | 0xa5.toByte, 23 | 0xa1.toByte, 24 | 0xe8.toByte, 25 | 0x47.toByte, 26 | 0xbf.toByte, 27 | 0x82.toByte, 28 | 0x20.toByte, 29 | 0x6a.toByte, 30 | 0x58.toByte, 31 | 0x56.toByte, 32 | 0x9c.toByte, 33 | 0x0a.toByte, 34 | 0x62.toByte, 35 | ) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "streaming", 3 | srcs = glob(["*.scala"]), 4 | visibility = [ 5 | "//src/com/komanov/mysql/streaming/bin:__pkg__", 6 | "//src/com/komanov/mysql/streaming/jmh:__pkg__", 7 | "//src/com/komanov/mysql/streaming/tests:__pkg__", 8 | ], 9 | deps = [ 10 | "@mysql_streaming_maven//:com_wix_wix_embedded_mysql", 11 | "@mysql_streaming_maven//:de_flapdoodle_embed_de_flapdoodle_embed_process", 12 | "@mysql_streaming_maven//:mysql_mysql_connector_java", 13 | "@mysql_streaming_maven//:org_drizzle_jdbc_drizzle_jdbc", 14 | "@mysql_streaming_maven//:org_mariadb_jdbc_mariadb_java_client", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/Drivers.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.streaming 2 | 3 | import com.komanov.mysql.streaming.Drivers._ 4 | 5 | object Drivers { 6 | 7 | val Host = "localhost" 8 | val Port = 3316 9 | val UserName = "sa" 10 | val Password = "sa" 11 | val DataBase = "streaming_test" 12 | 13 | val list = Seq( 14 | ConnectorJDriver, 15 | MariaDbDriver, 16 | DrizzleDriver 17 | ) 18 | 19 | } 20 | 21 | sealed trait MysqlDriver { 22 | def name: String 23 | 24 | def url: String 25 | } 26 | 27 | case object ConnectorJDriver extends MysqlDriver { 28 | override val name = "ConnectorJ" 29 | 30 | override val url = s"jdbc:mysql://$Host:$Port/$DataBase?user=$UserName&password=$Password&cacheServerConfiguration=true&createDatabaseIfNotExist=false" 31 | } 32 | 33 | case object MariaDbDriver extends MysqlDriver { 34 | override val name = "MariaDB" 35 | 36 | override val url = s"jdbc:mariadb://$Host:$Port/$DataBase?user=$UserName&password=$Password&fastConnect=true&tcpAbortiveClose=true" 37 | } 38 | 39 | case object DrizzleDriver extends MysqlDriver { 40 | override val name = "Drizzle" 41 | 42 | override val url = s"jdbc:drizzle://$UserName:$Password@$Host:$Port/$DataBase" 43 | } 44 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/MysqlRunner.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.streaming 2 | 3 | import com.wix.mysql.EmbeddedMysql 4 | import com.wix.mysql.EmbeddedMysql._ 5 | import com.wix.mysql.config.Charset 6 | import com.wix.mysql.config.MysqldConfig._ 7 | import com.wix.mysql.config.SchemaConfig._ 8 | import com.wix.mysql.distribution.Version 9 | 10 | object MysqlRunner { 11 | 12 | private lazy val mysql: EmbeddedMysql = { 13 | val config = aMysqldConfig(Version.v5_7_latest) 14 | .withPort(Drivers.Port) 15 | .withUser(Drivers.UserName, Drivers.Password) 16 | .withCharset(Charset.UTF8MB4) 17 | .build() 18 | 19 | val mysqld = anEmbeddedMysql(config) 20 | .start() 21 | 22 | mysqld.addSchema( 23 | aSchemaConfig(Drivers.DataBase) 24 | .withCommands(Query.CreateSql) 25 | .build() 26 | ) 27 | 28 | mysqld 29 | } 30 | 31 | def run(): Unit = { 32 | mysql.toString 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/README.md: -------------------------------------------------------------------------------- 1 | ### Mysql Streaming 2 | 3 | A source code for the article "The Cost of Streaming Data from MySQL" at [medium](https://medium.com/p/the-cost-of-streaming-data-from-mysql-65e875dac95). 4 | 5 | Recent charts for the article is at https://dkomanov.github.io/charts/mysql-streaming/. 6 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "memory-consumption-app", 3 | srcs = ["MemoryConsumptionApp.scala"], 4 | main_class = "com.komanov.mysql.streaming.bin.MemoryConsumptionApp", 5 | deps = [ 6 | "//src/com/komanov/mysql/streaming", 7 | "@mysql_streaming_maven//:com_wix_wix_embedded_mysql", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/bin/MemoryConsumptionApp.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.streaming.bin 2 | 3 | import com.komanov.mysql.streaming.{Drivers, MysqlRunner, Query} 4 | 5 | object MemoryConsumptionApp { 6 | 7 | def main(args: Array[String]) = { 8 | require(args.length == 3, usage) 9 | 10 | val driver = Drivers.list.find(_.name.equalsIgnoreCase(args(0))).getOrElse(printAndExit("Unknown driver")) 11 | val streaming = args(1) match { 12 | case "streaming" => true 13 | case "at-once" => false 14 | case _ => printAndExit("Unknown mode") 15 | } 16 | val oom = args(2) match { 17 | case "oom" => true 18 | case "success" => false 19 | case _ => printAndExit("Unknown result") 20 | } 21 | 22 | MysqlRunner.run() 23 | 24 | Query.prepareTable(driver, length = 1000) 25 | 26 | try { 27 | if (streaming) { 28 | Query.forEachMillionViaStreaming(driver) 29 | } else { 30 | Query.forEachMillionAtOnce(driver) 31 | } 32 | 33 | if (oom) { 34 | printAndExit("Expected OOM") 35 | } 36 | } catch { 37 | case _: OutOfMemoryError if oom => 38 | println(s"OOM occurred, success") 39 | } 40 | } 41 | 42 | private def usage: Nothing = { 43 | printAndExit( 44 | s"""Usage: DRIVER MODE RESULT 45 | DRIVER - one of: ${Drivers.list.map(_.name).mkString("| ")} 46 | MODE - one of: streaming | at-once 47 | RESULT - one of: oom | success 48 | """) 49 | } 50 | 51 | private def printAndExit(s: String): Nothing = { 52 | Console.err.println(s) 53 | System.exit(1) 54 | throw new RuntimeException("Unreachable!") 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/mysql/streaming", 6 | "@mysql_streaming_maven//:com_wix_wix_embedded_mysql", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/jmh/BenchmarkBase.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.streaming.jmh 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import com.komanov.mysql.streaming._ 6 | import org.openjdk.jmh.annotations._ 7 | 8 | @State(Scope.Benchmark) 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 11 | @Fork(value = 1, jvmArgs = Array("-Xmx1G")) 12 | @Threads(1) 13 | @Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS) 14 | @Warmup(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS) 15 | abstract class BenchmarkBase(driver: MysqlDriver) { 16 | 17 | OneTimeInitialization.initialize(driver) 18 | 19 | @Param(Array( 20 | "1", "2", "3", "4", "5", "6", "7", "8", "9", 21 | "10", "20", "30", "40", "50", "60", "70", "80", "90", 22 | "100", "200", "300", "400", "500", "600", "700", "800", "900", 23 | "1000" 24 | )) 25 | var limit: Int = 0 26 | 27 | @Benchmark 28 | def atOnce(): List[TestTableRow] = { 29 | Query.selectAtOnce(driver, limit) 30 | } 31 | 32 | @Benchmark 33 | def stream(): List[TestTableRow] = { 34 | Query.selectViaStreaming(driver, limit) 35 | } 36 | 37 | } 38 | 39 | private[jmh] object OneTimeInitialization { 40 | private var initialized = false 41 | 42 | def initialize(driver: MysqlDriver): Unit = synchronized { 43 | if (!initialized) { 44 | MysqlRunner.run() 45 | 46 | Query.clearTable(driver) 47 | Query.prepareTable(driver) 48 | 49 | initialized = true 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/jmh/ConnectorJBenchmark.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.streaming.jmh 2 | 3 | import com.komanov.mysql.streaming._ 4 | 5 | class ConnectorJBenchmark extends BenchmarkBase(ConnectorJDriver) 6 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/jmh/MariaDbBenchmark.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.streaming.jmh 2 | 3 | import com.komanov.mysql.streaming._ 4 | 5 | class MariaDbBenchmark extends BenchmarkBase(MariaDbDriver) 6 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/tests/BUILD: -------------------------------------------------------------------------------- 1 | #scala_specs2_junit( 2 | # name = "tests", 3 | # size = "medium", 4 | # srcs = glob(["*.scala"]), 5 | # runtime_deps = [ 6 | # "@mysql_streaming_maven//:mysql_mysql_connector_java", 7 | # "@mysql_streaming_maven//:org_drizzle_jdbc_drizzle_jdbc", 8 | # "@mysql_streaming_maven//:org_mariadb_jdbc_mariadb_java_client", 9 | # ], 10 | # deps = [ 11 | # "//src/com/komanov/mysql/streaming", 12 | # "@mysql_streaming_maven//:com_wix_wix_embedded_mysql", 13 | # ], 14 | #) 15 | -------------------------------------------------------------------------------- /src/com/komanov/mysql/streaming/tests/QueryTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.mysql.streaming.tests 2 | 3 | import com.komanov.mysql.streaming._ 4 | import org.specs2.mutable.SpecificationWithJUnit 5 | import org.specs2.specification.Scope 6 | import org.specs2.specification.core.Fragments 7 | 8 | import scala.collection.mutable 9 | 10 | class QueryTest extends SpecificationWithJUnit { 11 | 12 | sequential 13 | 14 | MysqlRunner.run() 15 | 16 | Fragments.foreach(Drivers.list) { driver => 17 | s"${driver.name}" should { 18 | s"prepare/select/clear database" in new ctx { 19 | Query.clearTable(driver) 20 | Query.prepareTable(driver) 21 | Query.selectAtOnce(driver) must be_===(Query.TestData) 22 | Query.selectViaStreaming(driver) must be_===(Query.TestData) 23 | Query.clearTable(driver) 24 | Query.selectAtOnce(driver) must beEmpty 25 | } 26 | 27 | s"stream everything" in new ctx { 28 | Query.clearTable(driver) 29 | Query.prepareTable(driver) 30 | val result = mutable.ListBuffer[TestTableRow]() 31 | Query.forEach(driver, row => result += row) 32 | result.toList must be_===(Query.TestData) 33 | } 34 | } 35 | } 36 | 37 | class ctx extends Scope { 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "nativeaccess", 3 | srcs = glob(["*.java"]), 4 | visibility = [ 5 | "//src/com/komanov/nativeaccess:__subpackages__", 6 | ], 7 | deps = [ 8 | "//rs/getloadavg:getloadavg_lib", 9 | "//src/com/komanov/offheap/alloc", 10 | "@apangin_nalim_repo//:nalim", 11 | "@nativeaccess//:com_github_jnr_jnr_ffi", 12 | "@nativeaccess//:com_nativelibs4java_bridj", 13 | "@nativeaccess//:net_java_dev_jna_jna", 14 | "@nativeaccess//:net_java_dev_jna_jna_platform", 15 | "@nativeaccess//:org_bytedeco_javacpp_presets_systems", 16 | "@nativeaccess//:org_bytedeco_javacpp_presets_systems_platform", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/BridjHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import org.bridj.BridJ; 4 | import org.bridj.CRuntime; 5 | import org.bridj.Pointer; 6 | import org.bridj.ann.Library; 7 | import org.bridj.ann.Name; 8 | import org.bridj.ann.Runtime; 9 | 10 | import static com.komanov.nativeaccess.Helper.ensureResultCode; 11 | 12 | public abstract class BridjHelper { 13 | private BridjHelper() { 14 | } 15 | 16 | private static final LibC libC = new LibC(); 17 | 18 | public static double getLoadAverage() { 19 | Pointer p = Pointer.allocateDoubles(3); 20 | ensureResultCode(libC.getloadavg(p, 3)); 21 | return p.getDouble(); 22 | } 23 | 24 | @Library("c") 25 | @Runtime(CRuntime.class) 26 | private static class LibC { 27 | static { 28 | BridJ.register(); 29 | } 30 | 31 | @Name("getloadavg") 32 | public native int getloadavg(Pointer r, int num); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/Helper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | class Helper { 4 | public static void ensureResultCode(int code) { 5 | if (code < 1) { 6 | throw new IllegalStateException("getloadavg returned: " + code); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/JavaCppHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import org.bytedeco.javacpp.linux; 4 | 5 | import static com.komanov.nativeaccess.Helper.ensureResultCode; 6 | 7 | public abstract class JavaCppHelper { 8 | private JavaCppHelper() { 9 | } 10 | 11 | public static double getLoadAverage() { 12 | double[] r = new double[3]; 13 | ensureResultCode(linux.getloadavg(r, r.length)); 14 | return r[0]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/JnaHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import com.sun.jna.Native; 4 | import com.sun.jna.platform.linux.LibC; 5 | 6 | import static com.komanov.nativeaccess.Helper.ensureResultCode; 7 | 8 | public abstract class JnaHelper { 9 | private JnaHelper() { 10 | } 11 | 12 | static { 13 | Native.register("c"); 14 | } 15 | 16 | private native static int getloadavg(double[] r, int num); 17 | 18 | public static double getLoadAverage() { 19 | double[] r = new double[3]; 20 | ensureResultCode(LibC.INSTANCE.getloadavg(r, r.length)); 21 | return r[0]; 22 | } 23 | 24 | public static double getLoadAverageDirect() { 25 | double[] r = new double[3]; 26 | ensureResultCode(getloadavg(r, r.length)); 27 | return r[0]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/JniHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import com.komanov.offheap.alloc.Allocator; 4 | 5 | import static com.komanov.nativeaccess.Helper.ensureResultCode; 6 | 7 | public abstract class JniHelper { 8 | static { 9 | System.loadLibrary("getloadavg_lib"); 10 | } 11 | 12 | private JniHelper() { 13 | } 14 | 15 | private static native int getloadavg(long address, int num); 16 | 17 | private static native int foo(int num); 18 | 19 | public static double getLoadAverage() { 20 | long address = Allocator.alloc(24); 21 | try { 22 | ensureResultCode(getloadavg(address, 3)); 23 | return Allocator.getDouble(address); 24 | } finally { 25 | Allocator.release(address); 26 | } 27 | } 28 | 29 | public static int emptyJniCall(int num) { 30 | return foo(num); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/JnrHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import com.komanov.offheap.alloc.Allocator; 4 | import jnr.ffi.Pointer; 5 | 6 | import static com.komanov.nativeaccess.Helper.ensureResultCode; 7 | 8 | public abstract class JnrHelper { 9 | private JnrHelper() { 10 | } 11 | 12 | public static double getLoadAverage() { 13 | long address = Allocator.alloc(24); 14 | try { 15 | Pointer p = Pointer.wrap(JnrLibC.RUNTIME, address, 24); 16 | ensureResultCode(JnrLibC.INSTANCE.getloadavg(p, 3)); 17 | return p.getDouble(0); 18 | } finally { 19 | Allocator.release(address); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/JnrLibC.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import jnr.ffi.CallingConvention; 4 | import jnr.ffi.LibraryLoader; 5 | import jnr.ffi.Pointer; 6 | import jnr.ffi.Runtime; 7 | 8 | public interface JnrLibC { 9 | JnrLibC INSTANCE = LibraryLoader.create(JnrLibC.class) 10 | .convention(CallingConvention.DEFAULT) 11 | .load("c"); 12 | 13 | Runtime RUNTIME = Runtime.getRuntime(INSTANCE); 14 | 15 | int getloadavg(Pointer r, int num); 16 | } 17 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/NalimHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import one.nalim.Link; 4 | import one.nalim.Linker; 5 | 6 | import static com.komanov.nativeaccess.Helper.ensureResultCode; 7 | 8 | public abstract class NalimHelper { 9 | public static double getLoadAverageDirect() { 10 | double[] r = new double[3]; 11 | ensureResultCode(Libc.getloadavg(r, r.length)); 12 | return r[0]; 13 | } 14 | 15 | public static double getLoadAverageViaWrapper() { 16 | double[] r = new double[3]; 17 | ensureResultCode(GetLoadAvgLib.raw_getloadavg(r, r.length)); 18 | return r[0]; 19 | } 20 | 21 | private static class Libc { 22 | @Link 23 | public static native int getloadavg(double[] result, int num); 24 | 25 | static { 26 | Linker.linkClass(Libc.class); 27 | } 28 | } 29 | 30 | private static class GetLoadAvgLib { 31 | @Link 32 | public static native int raw_getloadavg(double[] result, int num); 33 | 34 | static { 35 | System.loadLibrary("getloadavg_lib"); 36 | Linker.linkClass(GetLoadAvgLib.class); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/PureJavaHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.nativeaccess; 2 | 3 | import java.lang.management.ManagementFactory; 4 | 5 | public abstract class PureJavaHelper { 6 | private PureJavaHelper() { 7 | } 8 | 9 | public static double getLoadAverage() { 10 | return ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/README.md: -------------------------------------------------------------------------------- 1 | To run: 2 | ``` 3 | bazel run //src/com/komanov/nativeaccess/jmh 4 | ``` 5 | 6 | Inspired by https://github.com/zakgof/java-native-benchmark, but gradle is just a mess (I really tried, but in IntelliJ it's 4.8, latest is 7 and jmh plugin doesn't work properly.) 7 | -------------------------------------------------------------------------------- /src/com/komanov/nativeaccess/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/nativeaccess", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "offheap", 3 | srcs = glob([ 4 | "*.scala", 5 | "*.java", 6 | ]), 7 | visibility = [ 8 | "//src/com/komanov/offheap:__subpackages__", 9 | ], 10 | deps = [ 11 | "//src/com/komanov/offheap/alloc", 12 | "@offheap_maven//:io_netty_netty5_buffer", 13 | "@offheap_maven//:io_netty_netty5_common", 14 | "@offheap_maven//:io_netty_netty_buffer", 15 | "@offheap_maven//:io_netty_netty_common", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/Netty4UuidSet.java: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import java.util.AbstractSet; 6 | import java.util.Iterator; 7 | import java.util.UUID; 8 | 9 | public class Netty4UuidSet extends AbstractSet { 10 | private final ByteBuf buf; 11 | private final int size; 12 | 13 | public Netty4UuidSet(ByteBuf buf, int size) { 14 | this.buf = buf; 15 | this.size = size; 16 | } 17 | 18 | public boolean contains(UUID uuid) { 19 | int found = binarySearch0(uuid); 20 | return found >= 0 && found < size; 21 | } 22 | 23 | @Override 24 | public boolean contains(Object o) { 25 | return contains((UUID) o); 26 | } 27 | 28 | @Override 29 | public Iterator iterator() { 30 | throw new IllegalStateException("not implemented"); 31 | } 32 | 33 | @Override 34 | public int size() { 35 | return this.size; 36 | } 37 | 38 | public void destroy() { 39 | buf.release(); 40 | } 41 | 42 | private int binarySearch0(UUID key) { 43 | int low = 0; 44 | int high = size - 1; 45 | 46 | while (low <= high) { 47 | int mid = (low + high) >>> 1; 48 | 49 | int current = 16 * mid; 50 | long mostBits = buf.getLong(current); 51 | 52 | if (mostBits < key.getMostSignificantBits()) 53 | low = mid + 1; 54 | else if (mostBits > key.getMostSignificantBits()) 55 | high = mid - 1; 56 | else { 57 | long leastBits = buf.getLong(current + 8); 58 | if (leastBits < key.getLeastSignificantBits()) 59 | low = mid + 1; 60 | else if (leastBits > key.getLeastSignificantBits()) 61 | high = mid - 1; 62 | else 63 | return mid; // key found 64 | } 65 | } 66 | return -(low + 1); // key not found. 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/Netty5UuidSet.java: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap; 2 | 3 | import io.netty5.buffer.api.Buffer; 4 | 5 | import java.util.AbstractSet; 6 | import java.util.Iterator; 7 | import java.util.UUID; 8 | 9 | public class Netty5UuidSet extends AbstractSet { 10 | private final Buffer buf; 11 | private final int size; 12 | 13 | public Netty5UuidSet(Buffer buf, int size) { 14 | this.buf = buf; 15 | this.size = size; 16 | } 17 | 18 | public boolean contains(UUID uuid) { 19 | int found = binarySearch0(uuid); 20 | return found >= 0 && found < size; 21 | } 22 | 23 | @Override 24 | public boolean contains(Object o) { 25 | return contains((UUID) o); 26 | } 27 | 28 | @Override 29 | public Iterator iterator() { 30 | throw new IllegalStateException("not implemented"); 31 | } 32 | 33 | @Override 34 | public int size() { 35 | return this.size; 36 | } 37 | 38 | public void destroy() { 39 | buf.close(); 40 | } 41 | 42 | private int binarySearch0(UUID key) { 43 | int low = 0; 44 | int high = size - 1; 45 | 46 | while (low <= high) { 47 | int mid = (low + high) >>> 1; 48 | 49 | int current = 16 * mid; 50 | long mostBits = buf.getLong(current); 51 | 52 | if (mostBits < key.getMostSignificantBits()) 53 | low = mid + 1; 54 | else if (mostBits > key.getMostSignificantBits()) 55 | high = mid - 1; 56 | else { 57 | long leastBits = buf.getLong(current + 8); 58 | if (leastBits < key.getLeastSignificantBits()) 59 | low = mid + 1; 60 | else if (leastBits > key.getLeastSignificantBits()) 61 | high = mid - 1; 62 | else 63 | return mid; // key found 64 | } 65 | } 66 | return -(low + 1); // key not found. 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/OffHeapBasedUuidSet.java: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap; 2 | 3 | import com.komanov.offheap.alloc.Allocator; 4 | 5 | import java.util.AbstractSet; 6 | import java.util.Iterator; 7 | import java.util.UUID; 8 | 9 | public class OffHeapBasedUuidSet extends AbstractSet { 10 | 11 | private final long address; 12 | private final int size; 13 | 14 | OffHeapBasedUuidSet(long address, int size) { 15 | this.address = address; 16 | this.size = size; 17 | } 18 | 19 | @Override 20 | public boolean contains(Object o) { 21 | return contains((UUID) o); 22 | } 23 | 24 | public boolean contains(UUID value) { 25 | int found = binarySearch0(value); 26 | return found >= 0 && found < size; 27 | } 28 | 29 | private int binarySearch0(UUID key) { 30 | int low = 0; 31 | int high = size - 1; 32 | 33 | while (low <= high) { 34 | int mid = (low + high) >>> 1; 35 | 36 | long current = address + 16L * mid; 37 | long mostBits = Allocator.getLong(current); 38 | 39 | if (mostBits < key.getMostSignificantBits()) 40 | low = mid + 1; 41 | else if (mostBits > key.getMostSignificantBits()) 42 | high = mid - 1; 43 | else { 44 | long leastBits = Allocator.getLong(current + 8); 45 | if (leastBits < key.getLeastSignificantBits()) 46 | low = mid + 1; 47 | else if (leastBits > key.getLeastSignificantBits()) 48 | high = mid - 1; 49 | else 50 | return mid; // key found 51 | } 52 | } 53 | return -(low + 1); // key not found. 54 | } 55 | 56 | @Override 57 | public Iterator iterator() { 58 | throw new IllegalStateException("not implemented"); 59 | } 60 | 61 | @Override 62 | public int size() { 63 | return this.size; 64 | } 65 | 66 | public void destroy() { 67 | Allocator.release(address); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/SortedLongArrayUuidSet.java: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap; 2 | 3 | import java.util.Iterator; 4 | import java.util.UUID; 5 | 6 | public class SortedLongArrayUuidSet extends java.util.AbstractSet { 7 | private final long[] array; 8 | private final int size; 9 | 10 | public SortedLongArrayUuidSet(long[] array) { 11 | if (array.length % 2 != 0) { 12 | throw new IllegalArgumentException("array should be of even length"); 13 | } 14 | 15 | this.array = array; 16 | this.size = array.length / 2; 17 | } 18 | 19 | @Override 20 | public boolean contains(Object o) { 21 | return contains((UUID) o); 22 | } 23 | 24 | public boolean contains(UUID value) { 25 | int found = binarySearch0(value); 26 | return found >= 0 && found < size; 27 | } 28 | 29 | private int binarySearch0(UUID key) { 30 | int low = 0; 31 | int high = size - 1; 32 | 33 | while (low <= high) { 34 | int mid = (low + high) >>> 1; 35 | 36 | int current = 2 * mid; 37 | long mostBits = array[current]; 38 | 39 | if (mostBits < key.getMostSignificantBits()) 40 | low = mid + 1; 41 | else if (mostBits > key.getMostSignificantBits()) 42 | high = mid - 1; 43 | else { 44 | long leastBits = array[current + 1]; 45 | if (leastBits < key.getLeastSignificantBits()) 46 | low = mid + 1; 47 | else if (leastBits > key.getLeastSignificantBits()) 48 | high = mid - 1; 49 | else 50 | return mid; // key found 51 | } 52 | } 53 | return -(low + 1); // key not found. 54 | } 55 | 56 | @Override 57 | public Iterator iterator() { 58 | throw new IllegalStateException("not implemented"); 59 | } 60 | 61 | @Override 62 | public int size() { 63 | return this.size; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/SortedUuidArray.java: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap; 2 | 3 | import java.util.Arrays; 4 | import java.util.Iterator; 5 | import java.util.UUID; 6 | 7 | public class SortedUuidArray extends java.util.AbstractSet { 8 | private final UUID[] array; 9 | 10 | public SortedUuidArray(UUID[] array) { 11 | this.array = array; 12 | } 13 | 14 | @Override 15 | public boolean contains(Object o) { 16 | return contains((UUID) o); 17 | } 18 | 19 | public boolean contains(UUID value) { 20 | int found = Arrays.binarySearch(array, value); 21 | return found >= 0 && found < array.length; 22 | } 23 | 24 | @Override 25 | public Iterator iterator() { 26 | return Arrays.stream(array).iterator(); 27 | } 28 | 29 | @Override 30 | public int size() { 31 | return array.length; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/alloc/Allocator.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap.alloc 2 | 3 | import sun.misc.Unsafe 4 | 5 | object Allocator { 6 | val unsafe: Unsafe = { 7 | val field = classOf[Unsafe].getDeclaredField("theUnsafe") 8 | field.setAccessible(true) 9 | field.get(null).asInstanceOf[Unsafe] 10 | } 11 | 12 | def getLong(address: Long): Long = { 13 | unsafe.getLong(address) 14 | } 15 | 16 | def getDouble(address: Long): Double = { 17 | unsafe.getDouble(address); 18 | } 19 | 20 | def setLong(address: Long, value: Long): Unit = { 21 | unsafe.putLong(address, value) 22 | } 23 | 24 | def alloc(bytes: Long): Long = { 25 | unsafe.allocateMemory(bytes) 26 | } 27 | 28 | def release(address: Long): Unit = { 29 | unsafe.freeMemory(address) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/alloc/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "alloc", 3 | srcs = ["Allocator.scala"], 4 | visibility = ["//visibility:public"], 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/collection/jmh:helper", 6 | "//src/com/komanov/offheap", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/uuidhashmap/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "uuidhashmap", 3 | srcs = ["UuidToIntHashMap.java"], 4 | visibility = [ 5 | "//src/com/komanov/offheap:__subpackages__", 6 | ], 7 | deps = [ 8 | "//src/com/komanov/offheap/alloc", 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/uuidhashmap/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | ":helper", 6 | "//src/com/komanov/offheap/uuidhashmap", 7 | ], 8 | ) 9 | 10 | java_library( 11 | name = "helper", 12 | srcs = ["JavaHelper.java"], 13 | visibility = ["//visibility:public"], 14 | deps = [ 15 | "//src/com/komanov/offheap/uuidhashmap", 16 | "@io_bazel_rules_scala_scala_library", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/uuidhashmap/jmh/JavaHelper.java: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap.uuidhashmap.jmh; 2 | 3 | import com.komanov.offheap.uuidhashmap.UuidToIntHashMap; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.UUID; 8 | 9 | public class JavaHelper { 10 | public static int run(Map collection, List toCheck) { 11 | int r = 0; 12 | for (UUID uuid : toCheck) { 13 | if (collection.containsKey(uuid)) { 14 | r += 1; 15 | } 16 | } 17 | return r; 18 | } 19 | 20 | public static int run(scala.collection.immutable.Map collection, List toCheck) { 21 | int r = 0; 22 | for (UUID uuid : toCheck) { 23 | if (collection.contains(uuid)) { 24 | r += 1; 25 | } 26 | } 27 | return r; 28 | } 29 | 30 | public static int run(UuidToIntHashMap map, List toCheck) { 31 | int r = 0; 32 | for (UUID uuid : toCheck) { 33 | if (map.get(uuid).isPresent()) { 34 | r += 1; 35 | } 36 | } 37 | return r; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/uuidhashmap/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/offheap/uuidhashmap", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/offheap/uuidhashmap/tests/UuidToIntHashMapTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.offheap.uuidhashmap.tests 2 | 3 | import scala.jdk.CollectionConverters._ 4 | import com.komanov.offheap.uuidhashmap.UuidToIntHashMap 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | import org.specs2.specification.core.Fragments 7 | 8 | import java.util.UUID.randomUUID 9 | import java.util.{Collections, OptionalInt, UUID} 10 | 11 | class UuidToIntHashMapTest extends SpecificationWithJUnit { 12 | 13 | sequential 14 | 15 | Fragments.foreach(Seq( 16 | TestCase("on-heap", map => UuidToIntHashMap.heap(map)), 17 | TestCase("off-heap", map => UuidToIntHashMap.offHeap(map)), 18 | TestCase("off-heap with padding", map => UuidToIntHashMap.offHeapWithPadding(map)), 19 | )) { case TestCase(name, make) => 20 | name >> { 21 | "succeed for empty Map" >> { 22 | val map = make(Collections.emptyMap()) 23 | map.get(randomUUID) must beOptEmpty 24 | map.getClass.getSimpleName must contain("EmptyMap") 25 | } 26 | 27 | "succeed for a single item" >> { 28 | val key = randomUUID 29 | val value = 666 30 | val map = make(Collections.singletonMap(key, value)) 31 | try { 32 | map.get(randomUUID) must beOptEmpty 33 | map.get(key) must beOptOf(value) 34 | } finally { 35 | map.destroy() 36 | } 37 | } 38 | 39 | "succeed for a multiple items" >> { 40 | val items = (1 to 10).map(v => randomUUID -> v) 41 | val map = make(items.map(t => t._1 -> Int.box(t._2)).toMap.asJava) 42 | try { 43 | map.get(randomUUID) must beOptEmpty 44 | map.get(items.head._1) must beOptOf(items.head._2) 45 | items.map(_._1).map(map.get) must contain(exactly(items.map(_._2).map(beOptOf): _*)).inOrder 46 | } finally { 47 | map.destroy() 48 | } 49 | } 50 | } 51 | } 52 | 53 | private def beOptEmpty = ===(OptionalInt.empty()) 54 | private def beOptOf(v: Int) = ===(OptionalInt.of(v)) 55 | 56 | private case class TestCase(name: String, f: java.util.Map[UUID, Integer] => UuidToIntHashMap) 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "readlines", 3 | srcs = glob([ 4 | "*.scala", 5 | "*.java", 6 | ]), 7 | visibility = [ 8 | "//src/com/komanov/readlines/bin:__pkg__", 9 | "//src/com/komanov/readlines/jmh:__pkg__", 10 | "//src/com/komanov/readlines/tests:__pkg__", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/ReadLinesJavaStreams.java: -------------------------------------------------------------------------------- 1 | package com.komanov.readlines; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.util.function.Consumer; 7 | import java.util.stream.Stream; 8 | 9 | public class ReadLinesJavaStreams { 10 | 11 | public static void forEachJava(Path path, Consumer f) throws IOException { 12 | try (Stream lines = Files.lines(path)) { 13 | lines.forEach(f); 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "bin", 3 | srcs = glob([ 4 | "*.scala", 5 | "*.java", 6 | ]), 7 | main_class = "com.komanov.readlines.bin.TestApp", 8 | deps = [ 9 | "//src/com/komanov/readlines", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/bin/TestApp.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.readlines.bin 2 | 3 | import java.nio.ByteBuffer 4 | import java.nio.charset.StandardCharsets 5 | 6 | import com.komanov.readlines.ReadLinesUtils 7 | 8 | object TestApp extends App { 9 | 10 | // List(41, df, 6771, 10400) 11 | val bytes = List( 12 | 0x41, 13 | 0xC3, 0x9F, 14 | 0xE6, 0x9D, 0xB1, 15 | 0xF0, 0x90, 0x90, 0x80 16 | ) 17 | .map(_.toByte) 18 | .toArray 19 | 20 | val cs = StandardCharsets.UTF_8 21 | val s = cs.decode(ByteBuffer.wrap(bytes)).toString 22 | 23 | println(s) 24 | 25 | println(s.getBytes(cs).map(_.toInt.toHexString).toList) 26 | println(s.toCharArray.map(_.toInt.toHexString).toList) 27 | println(s.codePoints().toArray.map(_.toHexString).toList) 28 | 29 | ReadLinesUtils.decodeUtf8FromBytes(bytes, s => { 30 | println(s) 31 | println(s.codePoints().toArray.map(_.toHexString).toList) 32 | }) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/jmh/BUILD: -------------------------------------------------------------------------------- 1 | java_library( 2 | name = "lib", 3 | srcs = ["InputType.java"], 4 | ) 5 | 6 | scala_benchmark_jmh( 7 | name = "jmh", 8 | srcs = glob(["*.scala"]), 9 | deps = [ 10 | "lib", 11 | "//src/com/komanov/readlines", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/jmh/InputType.java: -------------------------------------------------------------------------------- 1 | package com.komanov.readlines.jmh; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Random; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.IntStream; 8 | 9 | public enum InputType { 10 | ASCII { 11 | @Override 12 | public int[] getCodePoints(int length) { 13 | return shuffled(IntStream.iterate(1, i -> i % 88 + 40).limit(length)); 14 | } 15 | }, 16 | SINGLE_CHAR_ONLY { 17 | @Override 18 | public int[] getCodePoints(int length) { 19 | return shuffled(IntStream.iterate(1, i -> (i % (Character.MAX_VALUE - 40)) + 40).limit(length)); 20 | } 21 | }, 22 | MIXED { 23 | @Override 24 | public int[] getCodePoints(int length) { 25 | return shuffled(IntStream.range(1, 0x8040).limit(length)); 26 | } 27 | }, 28 | /**/; 29 | 30 | private static final Random rnd = new Random(1234999); 31 | 32 | private static int[] shuffled(IntStream stream) { 33 | List numbers = stream.boxed().collect(Collectors.toList()); 34 | Collections.shuffle(numbers, rnd); 35 | return numbers.stream().mapToInt(i -> i).toArray(); 36 | } 37 | 38 | public abstract int[] getCodePoints(int length); 39 | } 40 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/jmh/ReadUtf8Benchmark.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.readlines.jmh 2 | 3 | import java.nio.ByteBuffer 4 | import java.nio.charset.StandardCharsets 5 | import java.util.concurrent.TimeUnit 6 | 7 | import com.komanov.readlines.{ReadUtf8Java, ReadUtf8Scala} 8 | import org.openjdk.jmh.annotations._ 9 | 10 | @State(Scope.Benchmark) 11 | @BenchmarkMode(Array(Mode.AverageTime)) 12 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 13 | @Fork(value = 1, jvmArgs = Array("-Xmx2G")) 14 | @Threads(1) 15 | @Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS) 16 | @Measurement(iterations = 7, time = 5, timeUnit = TimeUnit.SECONDS) 17 | class ReadUtf8Benchmark { 18 | 19 | @Param(Array("0", "1", "2", "5", "10", "100", "10000")) 20 | var lineLength: Int = _ 21 | @Param 22 | var inputType: InputType = _ 23 | 24 | private var bytes: Array[Byte] = _ 25 | 26 | @Setup 27 | def setup(): Unit = { 28 | val codePoints = inputType.getCodePoints(lineLength) 29 | bytes = new String(codePoints, 0, codePoints.length).getBytes(StandardCharsets.UTF_8) 30 | } 31 | 32 | @Benchmark 33 | def charset(): String = { 34 | StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString 35 | } 36 | 37 | @Benchmark 38 | def localMethodsScala(): String = { 39 | ReadUtf8Scala.localMethods(bytes) 40 | } 41 | 42 | @Benchmark 43 | def localMethodsIndices(): String = { 44 | ReadUtf8Scala.localMethodsIndices(bytes) 45 | } 46 | 47 | @Benchmark 48 | def sequentialLoopScala(): String = { 49 | ReadUtf8Scala.sequentialLoop(bytes) 50 | } 51 | 52 | @Benchmark 53 | def changeIndexInsideLoopScala(): String = { 54 | ReadUtf8Scala.changeIndexInsideLoop(bytes) 55 | } 56 | 57 | @Benchmark 58 | def changeIndexInsideLoopByteMagicScala(): String = { 59 | ReadUtf8Scala.changeIndexInsideLoopByteMagic(bytes) 60 | } 61 | 62 | @Benchmark 63 | def sequentialLoopJava(): String = { 64 | ReadUtf8Java.sequentialLoop(bytes) 65 | } 66 | 67 | @Benchmark 68 | def changeIndexInsideLoopJava(): String = { 69 | ReadUtf8Java.changeIndexInsideLoop(bytes) 70 | } 71 | 72 | @Benchmark 73 | def changeIndexInsideLoopByteMagicJava(): String = { 74 | ReadUtf8Java.changeIndexInsideLoopByteMagic(bytes) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/readlines", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/readlines/tests/ReadUtf8Test.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.readlines 2 | 3 | import java.nio.ByteBuffer 4 | import java.nio.charset.StandardCharsets 5 | 6 | import org.specs2.mutable.SpecificationWithJUnit 7 | import org.specs2.specification.core.Fragments 8 | 9 | import scala.util.Random 10 | 11 | class ReadUtf8Test extends SpecificationWithJUnit { 12 | 13 | Fragments.foreach(List( 14 | ReadUtfTestCase("charset", bytes => StandardCharsets.UTF_8.decode(ByteBuffer.wrap(bytes)).toString), 15 | ReadUtfTestCase("localMethodsScala", ReadUtf8Scala.localMethods), 16 | ReadUtfTestCase("localMethodsIndicesScala", ReadUtf8Scala.localMethodsIndices), 17 | ReadUtfTestCase("sequentialLoopScala", ReadUtf8Java.sequentialLoop), 18 | ReadUtfTestCase("changeIndexInsideLoopScala", ReadUtf8Java.changeIndexInsideLoop), 19 | ReadUtfTestCase("changeIndexInsideLoopByteMagicScala", ReadUtf8Java.changeIndexInsideLoopByteMagic), 20 | ReadUtfTestCase("sequentialLoopJava", ReadUtf8Java.sequentialLoop), 21 | ReadUtfTestCase("changeIndexInsideLoopJava", ReadUtf8Java.changeIndexInsideLoop), 22 | ReadUtfTestCase("changeIndexInsideLoopByteMagicJava", ReadUtf8Java.changeIndexInsideLoopByteMagic) 23 | )) { case ReadUtfTestCase(name, f) => 24 | name should { 25 | "convert correctly UTF-8 to String" >> { 26 | val bytes = List( 27 | 0x41, 28 | 0xC3, 0x9F, 29 | 0xE6, 0x9D, 0xB1, 30 | 0xF0, 0x90, 0x90, 0x80 31 | ) 32 | .map(_.toByte) 33 | .toArray[Byte] 34 | 35 | f(bytes) mustEqual "Aß東\uD801\uDC00" 36 | } 37 | 38 | "support all Unicode characters" >> { 39 | val codePoints = (40 to 0x8040).filter(Character.isValidCodePoint).toArray[Int] 40 | val s = new String(codePoints, 0, codePoints.length) 41 | f(s.getBytes(StandardCharsets.UTF_8)) mustEqual s 42 | } 43 | 44 | "support long lines" >> { 45 | val s = Random.alphanumeric.take(500).mkString 46 | f(s.getBytes(StandardCharsets.UTF_8)) mustEqual s 47 | } 48 | } 49 | } 50 | 51 | private case class ReadUtfTestCase(name: String, f: Array[Byte] => String) 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/com/komanov/redis/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//src/com/komanov/redis:__subpackages__"]) 2 | 3 | scala_library( 4 | name = "redis", 5 | srcs = glob([ 6 | "*.scala", 7 | "*.java", 8 | ]), 9 | deps = [ 10 | "@offheap_maven//:io_lettuce_lettuce_core", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /src/com/komanov/redis/README.md: -------------------------------------------------------------------------------- 1 | ## Redis Benchmark for Set (SISMEMBER command) 2 | 3 | Prepare data: 4 | ``` 5 | bazel run //src/com/komanov/redis/bin -- set-1m ~/uuid1m.txt 6 | bazel run //src/com/komanov/redis/bin -- set-10m ~/uuid10m.txt 7 | bazel run //src/com/komanov/redis/bin -- set-100k ~/uuid100k.txt 8 | ``` 9 | 10 | Run Redis in docker: 11 | ``` 12 | docker run --name my-redis -p 6379:6379 redis:7.0-alpine 13 | ``` 14 | 15 | Run cli: 16 | ``` 17 | docker exec -it my-redis redis-cli 18 | ``` 19 | 20 | Run benchmark: 21 | ``` 22 | time bazel run //src/com/komanov/redis/perf set-1m ~/uuid1m.txt 23 | time bazel run //src/com/komanov/redis/perf set-10m ~/uuid10m.txt 24 | time bazel run //src/com/komanov/redis/perf set-100k ~/uuid100k.txt 25 | ``` 26 | 27 | Results: https://docs.google.com/spreadsheets/d/1D5fhP-rxuxamOl58cGk7yLiLiViZnxWcE71klp6JC3I 28 | -------------------------------------------------------------------------------- /src/com/komanov/redis/StringUuidCodec.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.redis 2 | 3 | import io.lettuce.core.codec.{RedisCodec, StringCodec} 4 | 5 | import java.nio.ByteBuffer 6 | import java.util.UUID 7 | 8 | class StringUuidCodec extends RedisCodec[String, UUID] { 9 | override def decodeKey(bytes: ByteBuffer): String = 10 | StringCodec.ASCII.decodeKey(bytes) 11 | 12 | override def decodeValue(bytes: ByteBuffer): UUID = 13 | new UUID(bytes.getLong, bytes.getLong) 14 | 15 | override def encodeKey(key: String): ByteBuffer = 16 | StringCodec.ASCII.encodeKey(key) 17 | 18 | override def encodeValue(value: UUID): ByteBuffer = { 19 | val bb = ByteBuffer.allocate(16) 20 | bb.putLong(value.getMostSignificantBits) 21 | bb.putLong(value.getLeastSignificantBits) 22 | bb.rewind() 23 | bb 24 | } 25 | } 26 | 27 | object StringUuidCodec extends StringUuidCodec 28 | -------------------------------------------------------------------------------- /src/com/komanov/redis/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "bin", 3 | srcs = ["DataFiller.scala"], 4 | main_class = "com.komanov.redis.bin.DataFiller", 5 | deps = [ 6 | "//src/com/komanov/redis", 7 | "@offheap_maven//:io_lettuce_lettuce_core", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/redis/bin/DataFiller.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.redis.bin 2 | 3 | import com.komanov.redis.StringUuidCodec 4 | import io.lettuce.core.RedisClient 5 | import io.lettuce.core.api.StatefulRedisConnection 6 | 7 | import java.nio.file.{Files, Paths} 8 | import java.util.UUID 9 | import scala.collection.mutable 10 | 11 | // Usage: SET_NAME INPUT_FILE 12 | object DataFiller extends App { 13 | 14 | val Array(setName, input) = args 15 | 16 | val inputPath = Paths.get(input) 17 | require(Files.exists(inputPath), s"INPUT_FILE doesn't exist: $input") 18 | 19 | val client = RedisClient.create("redis://localhost:6379") 20 | val connection: StatefulRedisConnection[String, UUID] = client.connect(StringUuidCodec); 21 | val sync = connection.sync(); 22 | 23 | val chunk = mutable.ListBuffer[UUID]() 24 | 25 | def dumpChunk(): Unit = { 26 | val num = sync.sadd(setName, chunk.toSeq: _*) 27 | require(num == chunk.size, s"Failed to SADD: $num != ${chunk.size}") 28 | chunk.clear() 29 | } 30 | 31 | Files.lines(inputPath).forEach { line => 32 | chunk += UUID.fromString(line) 33 | if (chunk.size == 1000) { 34 | dumpChunk() 35 | } 36 | } 37 | if (chunk.nonEmpty) { 38 | dumpChunk() 39 | } 40 | 41 | client.shutdown() 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/com/komanov/redis/perf/BUILD: -------------------------------------------------------------------------------- 1 | java_binary( 2 | name = "perf", 3 | srcs = ["PerfTester.java"], 4 | main_class = "com.komanov.redis.perf.PerfTester", 5 | deps = [ 6 | "//src/com/komanov/redis", 7 | "@offheap_maven//:io_lettuce_lettuce_core", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/README.md: -------------------------------------------------------------------------------- 1 | # Scala serialization 2 | 3 | A source code for the article "Scala Serialization" at [medium](https://medium.com/@dkomanov/scala-serialization-419d175c888a). 4 | 5 | Recent charts for the article is at https://dkomanov.github.io/charts/scala-serialization/. 6 | 7 | Run: 8 | ``` 9 | JDK17_ARGS_APPEND="--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED" scripts/run-jmh.sh //src/com/komanov/serialization/jmh:jmh scala-serialization-2022 10 | ``` 11 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "common", 3 | srcs = ["package.scala"], 4 | deps = [ 5 | "//src/com/komanov/serialization/converters", 6 | ], 7 | ) 8 | 9 | scala_binary( 10 | name = "report-generator", 11 | srcs = ["ReportGenerator.scala"], 12 | jvm_flags = [ 13 | "--add-opens java.base/java.lang=ALL-UNNAMED", 14 | "--add-opens java.base/java.util=ALL-UNNAMED", 15 | "--add-opens java.base/java.lang.invoke=ALL-UNNAMED", 16 | "-Dfile.encoding=UTF-8", 17 | ], 18 | main_class = "com.komanov.serialization.bin.ReportGenerator", 19 | deps = [ 20 | ":common", 21 | "//src/com/komanov/serialization/converters", 22 | "//src/com/komanov/serialization/domain/testdata", 23 | "//src/com/komanov/serialization/io", 24 | ], 25 | ) 26 | 27 | scala_binary( 28 | name = "events-report-generator", 29 | srcs = ["EventsReportGenerator.scala"], 30 | jvm_flags = [ 31 | "--add-opens java.base/java.lang=ALL-UNNAMED", 32 | "--add-opens java.base/java.util=ALL-UNNAMED", 33 | "--add-opens java.base/java.lang.invoke=ALL-UNNAMED", 34 | "-Dfile.encoding=UTF-8", 35 | ], 36 | main_class = "com.komanov.serialization.bin.EventsReportGenerator", 37 | deps = [ 38 | ":common", 39 | "//src/com/komanov/serialization/converters", 40 | "//src/com/komanov/serialization/domain/testdata", 41 | "//src/com/komanov/serialization/io", 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/bin/package.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization 2 | 3 | import com.komanov.serialization.converters._ 4 | import com.komanov.serialization.converters.api.MyConverter 5 | 6 | import java.util 7 | import scala.jdk.CollectionConverters._ 8 | 9 | package object bin { 10 | val skip = new util.IdentityHashMap[MyConverter, Boolean](Seq( 11 | CapnprotoPooledConverter, 12 | CirceConverter, 13 | JsoniterScalaConverter, 14 | ScalaPbConverter, 15 | UpickleJsonConverter, 16 | UpicklePooledJsonConverter, 17 | ).map(_ -> true).toMap.asJava) 18 | } 19 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/capnproto/README.md: -------------------------------------------------------------------------------- 1 | # Cap’n Proto 2 | 3 | * https://capnproto.org/ 4 | * https://github.com/capnproto/capnproto-java 5 | 6 | ``` 7 | sudo apt install capnproto libcap 8 | ``` 9 | 10 | ``` 11 | capnpc -ojava -I ~/src/external/capnproto-java/compiler/src/main/schema common.capnp site.capnp events.capnp && mv *.java ../domain/capnproto/ 12 | ``` 13 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/capnproto/common.capnp: -------------------------------------------------------------------------------- 1 | @0x828a33383840f66f; 2 | 3 | using Java = import "/capnp/java.capnp"; 4 | $Java.package("com.komanov.serialization.domain.capnproto"); 5 | $Java.outerClassname("CommonCapnproto"); 6 | 7 | struct Uuid { 8 | mostSignificantBits @0 :Int64; 9 | leastSignificantBits @1 :Int64; 10 | } 11 | 12 | struct Instant { 13 | millis @0 :UInt64; 14 | } 15 | 16 | enum SiteType { 17 | unknownSiteType @0; 18 | flash @1; 19 | silverlight @2; 20 | html5 @3; 21 | } 22 | 23 | enum SiteFlag { 24 | unknownSiteFlag @0; 25 | free @1; 26 | premium @2; 27 | } 28 | 29 | enum PageComponentType { 30 | unknownPageComponentType @0; 31 | text @1; 32 | button @2; 33 | blog @3; 34 | } 35 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/capnproto/java.capnp: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Sandstorm Development Group, Inc. and contributors 2 | # Licensed under the MIT License: 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | @0xc5f1af96651f70ea; 23 | 24 | annotation package @0x9ee4c8f803b3b596 (file) : Text; 25 | # Name of the package, such as "org.example.foo", in which the generated code will reside. 26 | 27 | annotation outerClassname @0x9b066bb4881f7cd3 (file) : Text; 28 | # Name of the outer class that will wrap the generated code. 29 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/capnproto/site.capnp: -------------------------------------------------------------------------------- 1 | @0xa89cd67d322d666b; 2 | 3 | using Java = import "/capnp/java.capnp"; 4 | using Common = import "common.capnp"; 5 | $Java.package("com.komanov.serialization.domain.capnproto"); 6 | $Java.outerClassname("SiteCapnproto"); 7 | 8 | struct Domain { 9 | name @0 :Text; 10 | primary @1 :Bool; 11 | } 12 | 13 | struct EntryPoint { 14 | primary @0 :Bool; 15 | 16 | union { 17 | domain :group { 18 | domain @1 :Text; 19 | } 20 | free :group { 21 | userName @2 :Text; 22 | siteName @3 :Text; 23 | } 24 | } 25 | } 26 | 27 | struct MetaTag { 28 | name @0 :Text; 29 | value @1 :Text; 30 | } 31 | 32 | struct PageComponentData { 33 | union { 34 | text :group { 35 | text @0 :Text; 36 | } 37 | button :group { 38 | name @1 :Text; 39 | text @2 :Text; 40 | action @3 :Common.Uuid; 41 | } 42 | blog :group { 43 | name @4 :Text; 44 | rss @5 :Bool; 45 | tags @6 :Bool; 46 | } 47 | } 48 | } 49 | 50 | struct PageComponentPosition { 51 | x @0 :UInt32; 52 | y @1 :UInt32; 53 | } 54 | 55 | struct PageComponent { 56 | id @0 :Common.Uuid; 57 | componentType @1 :Common.PageComponentType; 58 | data @2 :PageComponentData; 59 | position @3 :PageComponentPosition; 60 | dateCreated @4 :Common.Instant; 61 | dateUpdated @5 :Common.Instant; 62 | } 63 | 64 | struct Page { 65 | name @0 :Text; 66 | path @1 :Text; 67 | metaTags @2 :List(MetaTag); 68 | components @3 :List(PageComponent); 69 | } 70 | 71 | # By some reason enum list doesn't work. 72 | struct SiteFlagWrapper { 73 | value @0 :Common.SiteFlag; 74 | } 75 | 76 | struct Site { 77 | id @0 :Common.Uuid; 78 | ownerId @1 :Common.Uuid; 79 | revision @2 :UInt64; 80 | siteType @3 :Common.SiteType; 81 | flags @4 :List(SiteFlagWrapper); 82 | name @5 :Text; 83 | description @6 :Text; 84 | domains @7 :List(Domain); 85 | defaultMetaTags @8 :List(MetaTag); 86 | pages @9 :List(Page); 87 | entryPoints @10 :List(EntryPoint); 88 | published @11 :Bool; 89 | dateCreated @12 :Common.Instant; 90 | dateUpdated @13 :Common.Instant; 91 | } 92 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/ChillConverter.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | import com.komanov.serialization.converters.api.MyConverter 4 | import com.komanov.serialization.domain.{Site, SiteEvent} 5 | import com.twitter.chill.ScalaKryoInstantiator 6 | 7 | /** https://github.com/twitter/chill */ 8 | object ChillConverter extends MyConverter { 9 | 10 | private val pool = ScalaKryoInstantiator.defaultPool 11 | 12 | override def toByteArray(site: Site): Array[Byte] = { 13 | pool.toBytesWithoutClass(site) 14 | } 15 | 16 | override def fromByteArray(bytes: Array[Byte]): Site = { 17 | pool.fromBytes(bytes, classOf[Site]) 18 | } 19 | 20 | override def toByteArray(event: SiteEvent): Array[Byte] = { 21 | pool.toBytesWithoutClass(event) 22 | } 23 | 24 | override def siteEventFromByteArray(clazz: Class[_], bytes: Array[Byte]): SiteEvent = { 25 | pool.fromBytes(bytes, clazz).asInstanceOf[SiteEvent] 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/CirceConverter.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | import com.komanov.serialization.converters.api.MyConverter 4 | import com.komanov.serialization.domain._ 5 | import io.circe.parser._ 6 | import io.circe.syntax._ 7 | 8 | import java.nio.charset.StandardCharsets 9 | import scala.util.Try 10 | 11 | /** https://circe.github.io/circe/ */ 12 | object CirceConverter extends MyConverter { 13 | 14 | import CirceImplicits._ 15 | 16 | override def toByteArray(site: Site): Array[Byte] = { 17 | require(encodeSite != null) 18 | site.asJson(encodeSite).noSpaces.getBytes(StandardCharsets.UTF_8) 19 | } 20 | 21 | override def fromByteArray(bytes: Array[Byte]): Site = 22 | decode[Site](new String(bytes, StandardCharsets.UTF_8)).fold(throw _, identity) 23 | 24 | override def toByteArray(event: SiteEvent): Array[Byte] = 25 | event.asJson.noSpaces.getBytes(StandardCharsets.UTF_8) 26 | 27 | override def siteEventFromByteArray(clazz: Class[_], bytes: Array[Byte]): SiteEvent = 28 | decode[SiteEvent](new String(bytes, StandardCharsets.UTF_8)).fold(throw _, identity) 29 | 30 | private object CirceImplicits { 31 | 32 | import io.circe._ 33 | import io.circe.generic.auto._ 34 | 35 | implicit val encodePageComponentType: Encoder[PageComponentType] = Encoder.encodeString.contramap[PageComponentType](_.toString) 36 | implicit val decodePageComponentType: Decoder[PageComponentType] = Decoder.decodeString.emapTry(v => Try(PageComponentType.valueOf(v))) 37 | 38 | implicit val encodeSiteFlag: Encoder[SiteFlag] = Encoder.encodeString.contramap[SiteFlag](_.toString) 39 | implicit val decodeSiteFlag: Decoder[SiteFlag] = Decoder.decodeString.emapTry(v => Try(SiteFlag.valueOf(v))) 40 | 41 | implicit val encodeSiteType: Encoder[SiteType] = Encoder.encodeString.contramap[SiteType](_.toString) 42 | implicit val decodeSiteType: Decoder[SiteType] = Decoder.decodeString.emapTry(v => Try(SiteType.valueOf(v))) 43 | 44 | implicit val encodeSite: Encoder[Site] = exportEncoder[Site].instance 45 | implicit val decodeSite: Decoder[Site] = exportDecoder[Site].instance 46 | 47 | implicit val encodeSiteEvent: Encoder[SiteEvent] = exportEncoder[SiteEvent].instance 48 | implicit val decodeSiteEvent: Decoder[SiteEvent] = exportDecoder[SiteEvent].instance 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/ConversionUtils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | import java.nio.{Buffer, ByteBuffer} 4 | import java.time.Instant 5 | import java.util.UUID 6 | 7 | object ConversionUtils { 8 | 9 | def uuidToByteBuffer(uuid: UUID): ByteBuffer = { 10 | if (uuid == null) { 11 | return null 12 | } 13 | 14 | val buffer = ByteBuffer.allocate(16) 15 | buffer.putLong(uuid.getMostSignificantBits) 16 | buffer.putLong(uuid.getLeastSignificantBits) 17 | // asInstanceOf is for JDK8 support: java.lang.NoSuchMethodError: java.nio.ByteBuffer.rewind()Ljava/nio/ByteBuffer; 18 | // After JDK8 it was overridden to return ByteBuffer instead of Buffer. 19 | // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/nio/ByteBuffer.html#rewind() 20 | // https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html 21 | buffer.asInstanceOf[Buffer].rewind() 22 | buffer 23 | } 24 | 25 | def bytesToUuid(bb: ByteBuffer): UUID = { 26 | if (bb == null) { 27 | return null 28 | } 29 | 30 | val length = bb.limit() - bb.position() 31 | if (length == 0) { 32 | return null 33 | } 34 | 35 | require(length >= 16, s"expected 16 bytes: ${bb.capacity()} / ${bb.limit()}") 36 | 37 | new UUID(bb.getLong, bb.getLong) 38 | } 39 | 40 | def instantToLong(v: Instant) = v.toEpochMilli 41 | 42 | def longToInstance(v: Long) = Instant.ofEpochMilli(v) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/Converters.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | import com.komanov.serialization.converters.api.MyConverter 4 | 5 | object Converters { 6 | 7 | val all: Seq[(String, MyConverter)] = Seq( 8 | "JSON" -> JsonConverter, 9 | "CBOR" -> JacksonCborConverter, 10 | "Smile" -> JacksonSmileConverter, 11 | "ScalaPB" -> ScalaPbConverter, 12 | "Java PB" -> JavaPbConverter, 13 | "Java Thrift" -> JavaThriftConverter, 14 | "Serializable" -> JavaSerializationConverter, 15 | "BooPickle" -> BoopickleConverter, 16 | "Chill" -> ChillConverter, 17 | "Jsoniter" -> JsoniterScalaConverter, 18 | "Circe" -> CirceConverter, 19 | "uPickle" -> UpickleJsonConverter, 20 | "uPickle pooled" -> UpicklePooledJsonConverter, 21 | "uPickle MsgPack" -> UpickleMsgpackConverter, 22 | "Cap'n Proto" -> CapnprotoConverter, 23 | "Cap'n Proto pooled" -> CapnprotoPooledConverter, 24 | ) 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/JavaSerializationConverter.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream} 4 | 5 | import com.komanov.serialization.converters.api.MyConverter 6 | import com.komanov.serialization.io.IoUtils.using 7 | import com.komanov.serialization.domain.{Site, SiteEvent, SiteEventData} 8 | 9 | object JavaSerializationConverter extends MyConverter { 10 | 11 | override def toByteArray(site: Site): Array[Byte] = { 12 | using(new ByteArrayOutputStream()) { baos => 13 | using(new ObjectOutputStream(baos)) { os => 14 | os.writeObject(site) 15 | os.flush() 16 | baos.toByteArray 17 | } 18 | } 19 | } 20 | 21 | override def fromByteArray(bytes: Array[Byte]): Site = { 22 | using(new ByteArrayInputStream(bytes)) { bais => 23 | using(new ObjectInputStream(bais)) { os => 24 | os.readObject().asInstanceOf[Site] 25 | } 26 | } 27 | } 28 | 29 | override def toByteArray(event: SiteEvent): Array[Byte] = { 30 | using(new ByteArrayOutputStream()) { baos => 31 | using(new ObjectOutputStream(baos)) { os => 32 | os.writeObject(event) 33 | os.flush() 34 | baos.toByteArray 35 | } 36 | } 37 | } 38 | 39 | override def siteEventFromByteArray(clazz: Class[_], bytes: Array[Byte]): SiteEvent = { 40 | using(new ByteArrayInputStream(bytes)) { bais => 41 | using(new ObjectInputStream(bais)) { os => 42 | os.readObject().asInstanceOf[SiteEvent] 43 | } 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/JsoniterScalaConverter.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | import java.time.Instant 4 | 5 | import com.github.plokhotnyuk.jsoniter_scala.core._ 6 | import com.github.plokhotnyuk.jsoniter_scala.macros._ 7 | import com.komanov.serialization.converters.api.MyConverter 8 | import com.komanov.serialization.domain.{Site, SiteEvent} 9 | 10 | /** https://github.com/plokhotnyuk/jsoniter-scala */ 11 | object JsoniterScalaConverter extends MyConverter { 12 | private val writerConfig = WriterConfig 13 | private val readerConfig = ReaderConfig 14 | 15 | private implicit val instantCodec: JsonValueCodec[Instant] = new JsonValueCodec[Instant] { 16 | override def nullValue: Instant = null 17 | override def decodeValue(in: JsonReader, default: Instant): Instant = Instant.ofEpochMilli(in.readLong()) 18 | override def encodeValue(x: Instant, out: JsonWriter): Unit = out.writeVal(x.toEpochMilli) 19 | } 20 | private implicit val siteCodec: JsonValueCodec[Site] = JsonCodecMaker.make[Site](CodecMakerConfig) 21 | private implicit val siteEventCodec: JsonValueCodec[SiteEvent] = JsonCodecMaker.make[SiteEvent](CodecMakerConfig) 22 | 23 | def toByteArray(site: Site): Array[Byte] = writeToArray(site, writerConfig) 24 | 25 | def fromByteArray(bytes: Array[Byte]): Site = readFromArray[Site](bytes, readerConfig) 26 | 27 | def toByteArray(event: SiteEvent): Array[Byte] = writeToArray(event, writerConfig) 28 | 29 | def siteEventFromByteArray(clazz: Class[_], bytes: Array[Byte]): SiteEvent = readFromArray[SiteEvent](bytes, readerConfig) 30 | } 31 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/ProtobufConversionUtils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | import java.util.UUID 4 | 5 | import com.google.protobuf.ByteString 6 | 7 | object ProtobufConversionUtils { 8 | def uuidToBytes(uuid: UUID): ByteString = { 9 | val bb = ConversionUtils.uuidToByteBuffer(uuid) 10 | if (bb == null) ByteString.EMPTY else ByteString.copyFrom(bb) 11 | } 12 | 13 | def bytesToUuid(bs: ByteString): UUID = { 14 | ConversionUtils.bytesToUuid(bs.asReadOnlyByteBuffer()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/ReflectionUtils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters 2 | 3 | private[converters] object ReflectionUtils { 4 | 5 | def getCompanionObject(clazz: Class[_]): Any = { 6 | import scala.reflect.runtime.{currentMirror => cm} 7 | val classSymbol = cm.classSymbol(clazz) 8 | val moduleSymbol = classSymbol.companion.asModule 9 | val moduleMirror = cm.reflectModule(moduleSymbol) 10 | moduleMirror.instance 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/api/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "api", 3 | srcs = glob(["*.scala"]), 4 | visibility = ["//src/com/komanov/serialization:__subpackages__"], 5 | exports = [ 6 | "//src/com/komanov/serialization/domain", 7 | ], 8 | deps = [ 9 | "//src/com/komanov/serialization/domain", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/api/EventConverter.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters.api 2 | 3 | import com.komanov.serialization.domain.SiteEvent 4 | 5 | trait EventConverter { 6 | 7 | def toByteArray(event: SiteEvent): Array[Byte] 8 | 9 | def siteEventFromByteArray(clazz: Class[_], bytes: Array[Byte]): SiteEvent 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/api/MyConverter.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters.api 2 | 3 | trait MyConverter 4 | extends SiteConverter 5 | with EventConverter 6 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/api/SiteConverter.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters.api 2 | 3 | import com.komanov.serialization.domain.Site 4 | 5 | trait SiteConverter { 6 | 7 | def toByteArray(site: Site): Array[Byte] 8 | 9 | def fromByteArray(bytes: Array[Byte]): Site 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | size = "medium", 4 | srcs = glob(["*.scala"]), 5 | jvm_flags = [ 6 | "--add-opens java.base/java.lang=ALL-UNNAMED", 7 | "--add-opens java.base/java.util=ALL-UNNAMED", 8 | "--add-opens java.base/java.lang.invoke=ALL-UNNAMED", 9 | "-Dfile.encoding=UTF-8", 10 | ], 11 | runtime_deps = [ 12 | "@scala_serialization_maven//:org_slf4j_slf4j_api", 13 | ], 14 | deps = [ 15 | "//src/com/komanov/serialization/converters", 16 | "//src/com/komanov/serialization/converters:conversion_utils", 17 | "//src/com/komanov/serialization/converters:protobuf_conversion_utils", 18 | "//src/com/komanov/serialization/domain", 19 | "//src/com/komanov/serialization/domain:domain_java_thrift", 20 | "//src/com/komanov/serialization/domain/testdata", 21 | "//src/com/komanov/serialization/proto:protos_java", 22 | "//src/com/komanov/serialization/proto:protos_scala", 23 | "@scala_serialization_maven//:com_fasterxml_jackson_core_jackson_core", 24 | "@scala_serialization_maven//:com_fasterxml_jackson_core_jackson_databind", 25 | "@scala_serialization_maven//:com_fasterxml_jackson_module_jackson_module_scala_2_13", 26 | "@scala_serialization_maven//:com_twitter_chill_2_13", 27 | "@scala_serialization_maven//:io_suzaku_boopickle_2_13", 28 | "@scala_serialization_maven//:org_apache_thrift_libthrift", 29 | ], 30 | ) 31 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/tests/ConversionsUtilsTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters.tests 2 | 3 | import java.time.Instant 4 | import java.util.UUID 5 | 6 | import com.komanov.serialization.converters._ 7 | import org.specs2.mutable.SpecWithJUnit 8 | 9 | class ConversionsUtilsTest extends SpecWithJUnit { 10 | 11 | "UUID" should { 12 | "be serialized-parsed" >> { 13 | val zero = new UUID(0, 0) 14 | val rnd = UUID.randomUUID() 15 | ConversionUtils.bytesToUuid(ConversionUtils.uuidToByteBuffer(null)) must beNull 16 | ConversionUtils.bytesToUuid(ConversionUtils.uuidToByteBuffer(zero)) must be_===(zero) 17 | ConversionUtils.bytesToUuid(ConversionUtils.uuidToByteBuffer(rnd)) must be_===(rnd) 18 | } 19 | } 20 | 21 | "Instant" should { 22 | "be serialized-parsed" >> { 23 | val zero = Instant.ofEpochMilli(0) 24 | val now = Instant.now() 25 | ConversionUtils.longToInstance(ConversionUtils.instantToLong(zero)) must be_===(zero) 26 | ConversionUtils.longToInstance(ConversionUtils.instantToLong(now)) must be_===(Instant.ofEpochMilli(now.toEpochMilli)) 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/converters/tests/ProtobufConversionsUtilsTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.converters.tests 2 | 3 | import java.util.UUID 4 | 5 | import com.komanov.serialization.converters.ProtobufConversionUtils 6 | import org.specs2.mutable.SpecWithJUnit 7 | 8 | class ProtobufConversionsUtilsTest extends SpecWithJUnit { 9 | 10 | "UUID" should { 11 | "be serialized-parsed" >> { 12 | val zero = new UUID(0, 0) 13 | val rnd = UUID.randomUUID() 14 | 15 | ProtobufConversionUtils.bytesToUuid(ProtobufConversionUtils.uuidToBytes(null)) must beNull 16 | ProtobufConversionUtils.bytesToUuid(ProtobufConversionUtils.uuidToBytes(zero)) must be_===(zero) 17 | ProtobufConversionUtils.bytesToUuid(ProtobufConversionUtils.uuidToBytes(rnd)) must be_===(rnd) 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//src/com/komanov/serialization:__subpackages__"]) 2 | 3 | java_library( 4 | name = "domain_java_thrift", 5 | srcs = glob(["thrift/*.java"]), 6 | deps = [ 7 | "@scala_serialization_maven//:javax_annotation_javax_annotation_api", 8 | "@scala_serialization_maven//:org_apache_thrift_libthrift", 9 | "@scala_serialization_maven//:org_slf4j_slf4j_api", 10 | ], 11 | ) 12 | 13 | scala_library( 14 | name = "domain", 15 | srcs = glob([ 16 | "*.scala", 17 | "*.java", 18 | ]), 19 | exports = [ 20 | "@scala_serialization_maven//:com_fasterxml_jackson_core_jackson_annotations", 21 | ], 22 | deps = [ 23 | "@scala_serialization_maven//:com_fasterxml_jackson_core_jackson_annotations", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/PageComponentType.java: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.domain; 2 | 3 | public enum PageComponentType { 4 | Unknown, 5 | Text, 6 | Button, 7 | Blog, 8 | } 9 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/SiteFlag.java: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.domain; 2 | 3 | public enum SiteFlag { 4 | Unknown, 5 | Free, 6 | Premium, 7 | } 8 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/SiteType.java: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.domain; 2 | 3 | public enum SiteType { 4 | Unknown, 5 | Flash, 6 | Silverlight, 7 | Html5, 8 | } 9 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/capnproto/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//src/com/komanov/serialization:__subpackages__"]) 2 | 3 | java_library( 4 | name = "capnproto", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "@scala_serialization_maven//:org_capnproto_runtime", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/testdata/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//src/com/komanov/serialization:__subpackages__"]) 2 | 3 | scala_library( 4 | name = "testdata", 5 | srcs = glob(["*.scala"]), 6 | deps = ["//src/com/komanov/serialization/domain"], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/serialization/domain", 6 | "//src/com/komanov/serialization/domain/testdata", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/tests/EventProcessorTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.domain.tests 2 | 3 | import com.komanov.serialization.domain.EventProcessor 4 | import com.komanov.serialization.domain.testdata.TestData 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | import org.specs2.specification.core.Fragments 7 | 8 | class EventProcessorTest extends SpecificationWithJUnit { 9 | 10 | "apply/unapply" should { 11 | Fragments.foreach(TestData.sites) { case (name, site) => 12 | s"serialize and deserialize a site [$name]" in { 13 | val parsed = EventProcessor.apply(EventProcessor.unapply(site)) 14 | parsed must be_===(site) 15 | } 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/thrift/PageComponentTypePb.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Autogenerated by Thrift Compiler (0.16.0) 3 | * 4 | * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | * @generated 6 | */ 7 | package com.komanov.serialization.domain.thrift; 8 | 9 | 10 | @javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.16.0)", date = "2022-08-14") 11 | public enum PageComponentTypePb implements org.apache.thrift.TEnum { 12 | UnknownPageComponentType(0), 13 | Text(1), 14 | Button(2), 15 | Blog(3); 16 | 17 | private final int value; 18 | 19 | private PageComponentTypePb(int value) { 20 | this.value = value; 21 | } 22 | 23 | /** 24 | * Get the integer value of this enum value, as defined in the Thrift IDL. 25 | */ 26 | public int getValue() { 27 | return value; 28 | } 29 | 30 | /** 31 | * Find a the enum type by its integer value, as defined in the Thrift IDL. 32 | * @return null if the value is not found. 33 | */ 34 | @org.apache.thrift.annotation.Nullable 35 | public static PageComponentTypePb findByValue(int value) { 36 | switch (value) { 37 | case 0: 38 | return UnknownPageComponentType; 39 | case 1: 40 | return Text; 41 | case 2: 42 | return Button; 43 | case 3: 44 | return Blog; 45 | default: 46 | return null; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/thrift/SiteFlagPb.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Autogenerated by Thrift Compiler (0.16.0) 3 | * 4 | * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | * @generated 6 | */ 7 | package com.komanov.serialization.domain.thrift; 8 | 9 | 10 | @javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.16.0)", date = "2022-08-14") 11 | public enum SiteFlagPb implements org.apache.thrift.TEnum { 12 | UnknownSiteFlag(0), 13 | Free(1), 14 | Premium(2); 15 | 16 | private final int value; 17 | 18 | private SiteFlagPb(int value) { 19 | this.value = value; 20 | } 21 | 22 | /** 23 | * Get the integer value of this enum value, as defined in the Thrift IDL. 24 | */ 25 | public int getValue() { 26 | return value; 27 | } 28 | 29 | /** 30 | * Find a the enum type by its integer value, as defined in the Thrift IDL. 31 | * @return null if the value is not found. 32 | */ 33 | @org.apache.thrift.annotation.Nullable 34 | public static SiteFlagPb findByValue(int value) { 35 | switch (value) { 36 | case 0: 37 | return UnknownSiteFlag; 38 | case 1: 39 | return Free; 40 | case 2: 41 | return Premium; 42 | default: 43 | return null; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/domain/thrift/SiteTypePb.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Autogenerated by Thrift Compiler (0.16.0) 3 | * 4 | * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | * @generated 6 | */ 7 | package com.komanov.serialization.domain.thrift; 8 | 9 | 10 | @javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.16.0)", date = "2022-08-14") 11 | public enum SiteTypePb implements org.apache.thrift.TEnum { 12 | UnknownSiteType(0), 13 | Flash(1), 14 | Silverlight(2), 15 | Html5(3); 16 | 17 | private final int value; 18 | 19 | private SiteTypePb(int value) { 20 | this.value = value; 21 | } 22 | 23 | /** 24 | * Get the integer value of this enum value, as defined in the Thrift IDL. 25 | */ 26 | public int getValue() { 27 | return value; 28 | } 29 | 30 | /** 31 | * Find a the enum type by its integer value, as defined in the Thrift IDL. 32 | * @return null if the value is not found. 33 | */ 34 | @org.apache.thrift.annotation.Nullable 35 | public static SiteTypePb findByValue(int value) { 36 | switch (value) { 37 | case 0: 38 | return UnknownSiteType; 39 | case 1: 40 | return Flash; 41 | case 2: 42 | return Silverlight; 43 | case 3: 44 | return Html5; 45 | default: 46 | return null; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/io/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "io", 3 | srcs = ["IoUtils.scala"], 4 | visibility = ["//src/com/komanov/serialization:__subpackages__"], 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/io/IoUtils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.io 2 | 3 | object IoUtils { 4 | 5 | def using[T <: AutoCloseable, K](stream: => T)(f: T => K): K = { 6 | var s = null.asInstanceOf[T] 7 | try { 8 | s = stream 9 | f(s) 10 | } finally { 11 | if (s != null) { 12 | s.close() 13 | } 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/jmh/BUILD: -------------------------------------------------------------------------------- 1 | enums_deps = [ 2 | "//src/com/komanov/serialization/converters", 3 | "//src/com/komanov/serialization/domain", 4 | "//src/com/komanov/serialization/domain/testdata", 5 | ] 6 | 7 | scala_library( 8 | name = "enums", 9 | srcs = glob(["*.java"]), 10 | visibility = ["//src/com/komanov/serialization/jmh:__subpackages__"], 11 | exports = enums_deps, 12 | deps = enums_deps, 13 | ) 14 | 15 | scala_benchmark_jmh( 16 | name = "jmh", 17 | srcs = glob(["*.scala"]), 18 | deps = [ 19 | ":enums", 20 | "//src/com/komanov/serialization/converters:scala_pb", 21 | "//src/com/komanov/serialization/proto:protos_scala", 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/jmh/ConverterType.java: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.jmh; 2 | 3 | import com.komanov.serialization.converters.*; 4 | import com.komanov.serialization.converters.api.MyConverter; 5 | 6 | public enum ConverterType { 7 | JSON(JsonConverter$.MODULE$), 8 | CBOR(JacksonCborConverter$.MODULE$), 9 | SMILE(JacksonSmileConverter$.MODULE$), 10 | SCALA_PB(ScalaPbConverter$.MODULE$), 11 | JAVA_PB(JavaPbConverter$.MODULE$), 12 | JAVA_THRIFT(JavaThriftConverter$.MODULE$), 13 | SERIALIZABLE(JavaSerializationConverter$.MODULE$), 14 | BOOPICKLE(BoopickleConverter$.MODULE$), 15 | CHILL(ChillConverter$.MODULE$), 16 | JSONITER(JsoniterScalaConverter$.MODULE$), 17 | CIRCE(CirceConverter$.MODULE$), 18 | UPICKLE_JSON(UpickleJsonConverter$.MODULE$), 19 | UPICKLE_POOLED_JSON(UpicklePooledJsonConverter$.MODULE$), 20 | UPICKLE_MSGPACK(UpickleMsgpackConverter$.MODULE$), 21 | CAP_N_PROTO(CapnprotoConverter$.MODULE$), 22 | CAP_N_PROTO_POOLED(CapnprotoPooledConverter$.MODULE$), 23 | /**/; 24 | 25 | public final MyConverter converter; 26 | 27 | ConverterType(MyConverter converter) { 28 | this.converter = converter; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/jmh/InputType.java: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.jmh; 2 | 3 | import com.komanov.serialization.domain.Site; 4 | import com.komanov.serialization.domain.testdata.TestData$; 5 | 6 | public enum InputType { 7 | _1_K(TestData$.MODULE$.site1k()), 8 | _2_K(TestData$.MODULE$.site2k()), 9 | _4_K(TestData$.MODULE$.site4k()), 10 | _8_K(TestData$.MODULE$.site8k()), 11 | _64_K(TestData$.MODULE$.site64k()), 12 | /**/; 13 | 14 | public final Site site; 15 | 16 | InputType(Site site) { 17 | this.site = site; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/jmh/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/serialization/jmh:enums", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/jmh/tests/ConverterTypeTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.serialization.jmh.tests 2 | 3 | import com.komanov.serialization.converters.Converters 4 | import com.komanov.serialization.jmh.ConverterType 5 | import org.specs2.mutable.SpecWithJUnit 6 | 7 | class ConverterTypeTest extends SpecWithJUnit { 8 | 9 | "ConverterType" should { 10 | "contain all converters" >> { 11 | ConverterType.values().map(_.converter).toSet mustEqual Converters.all.map(_._2).toSet 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/com/komanov/serialization/proto/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_scala//scala_proto:scala_proto.bzl", "scala_proto_library") 2 | load("@io_bazel_rules_scala//thrift:thrift.bzl", "thrift_library") 3 | 4 | proto_library( 5 | name = "protos", 6 | srcs = [ 7 | "events.proto", 8 | "site.proto", 9 | ], 10 | ) 11 | 12 | java_proto_library( 13 | name = "protos_java", 14 | visibility = ["//src/com/komanov/serialization:__subpackages__"], 15 | deps = [":protos"], 16 | ) 17 | 18 | scala_proto_library( 19 | name = "protos_scala", 20 | visibility = ["//src/com/komanov/serialization:__subpackages__"], 21 | deps = [":protos"], 22 | ) 23 | 24 | thrift_library( 25 | name = "thrift", 26 | srcs = [ 27 | "events.thrift", 28 | "site.thrift", 29 | ], 30 | ) 31 | -------------------------------------------------------------------------------- /src/com/komanov/str/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "@compression//:org_apache_commons_commons_lang3", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "stringformat", 3 | srcs = glob([ 4 | "*.scala", 5 | "*.java", 6 | ]), 7 | visibility = [ 8 | "//src/com/komanov/stringformat/bin:__pkg__", 9 | "//src/com/komanov/stringformat/jmh:__pkg__", 10 | "//src/com/komanov/stringformat/tests:__pkg__", 11 | ], 12 | deps = [ 13 | "@maven//:org_slf4j_slf4j_api", 14 | "//src/com/komanov/stringformat/macros", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/InputArg.java: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat; 2 | 3 | public enum InputArg { 4 | Tiny(1, ""), 5 | VeryShort(1, "12345"), 6 | Short(100, "string__10"), 7 | Medium(10000, "string________________________32"), 8 | Long(100000, "string___________________________________________________________________________________________100"), 9 | VeryLong(10000000, "string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495"), 10 | VeryLongSizeMiss(Integer.MAX_VALUE, "string______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________495"), 11 | /*IDEA*/; 12 | 13 | public final int value1; 14 | public final String value2; 15 | 16 | InputArg(int value1, String value2) { 17 | this.value1 = value1; 18 | this.value2 = value2; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/JavaFormats.java: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat; 2 | 3 | import org.slf4j.helpers.MessageFormatter; 4 | 5 | import java.text.MessageFormat; 6 | import java.util.Locale; 7 | 8 | public class JavaFormats { 9 | public static String concat(int value1, String value2, Object nullObject) { 10 | return value1 + "a" + value2 + "b" + value2 + nullObject; 11 | } 12 | 13 | public static String stringFormat(int value1, String value2, Object nullObject) { 14 | return String.format(Locale.ENGLISH, "%da%sb%s%s", value1, value2, value2, nullObject); 15 | } 16 | 17 | public static String messageFormat(int value1, String value2, Object nullObject) { 18 | return MessageFormat.format("{0,number,#}a{1}b{2}{3}", value1, value2, value2, nullObject); 19 | } 20 | 21 | public static String slf4j(int value1, String value2, Object nullObject) { 22 | return MessageFormatter 23 | .arrayFormat("{}a{}b{}{}", new Object[]{value1, value2, value2, nullObject}) 24 | .getMessage(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/README.md: -------------------------------------------------------------------------------- 1 | Optimizations described in the blog post are adopted in [scala upstream](https://github.com/scala/scala/pull/6093). This code is just for history purposed. 2 | 3 | A source code for the article "Scala: String Interpolation Performance" at [medium]([https://medium.com/@dkomanov/scala-serialization-419d175c888a](https://medium.com/@dkomanov/scala-string-interpolation-performance-21dc85e83afd)). [Charts](https://komanov.com/charts/scala-string-format/). 4 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/ScalaFormats.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat 2 | 3 | import com.komanov.stringformat.macros.MacroConcat._ 4 | 5 | object ScalaFormats { 6 | 7 | def concat(value1: Int, value2: String, nullObject: Object): String = { 8 | value1 + "a" + value2 + "b" + value2 + nullObject 9 | } 10 | 11 | def optimizedConcat1(value1: Int, value2: String, nullObject: Object): String = { 12 | OptimizedConcatenation1.concat(Int.box(value1), "a", value2, "b", value2, nullObject) 13 | } 14 | 15 | def optimizedConcat2(value1: Int, value2: String, nullObject: Object): String = { 16 | OptimizedConcatenation2.concat(Int.box(value1), "a", value2, "b", value2, nullObject) 17 | } 18 | 19 | def optimizedConcatMacros(value1: Int, value2: String, nullObject: Object): String = { 20 | so"${value1}a${value2}b$value2$nullObject" 21 | } 22 | 23 | def sInterpolator(value1: Int, value2: String, nullObject: Object): String = { 24 | s"${value1}a${value2}b$value2$nullObject" 25 | } 26 | 27 | def fInterpolator(value1: Int, value2: String, nullObject: Object): String = { 28 | f"${value1}a${value2}b$value2$nullObject" 29 | } 30 | 31 | def rawInterpolator(value1: Int, value2: String, nullObject: Object): String = { 32 | raw"${value1}a${value2}b$value2$nullObject" 33 | } 34 | 35 | def sfiInterpolator(value1: Int, value2: String, nullObject: Object): String = { 36 | sfi"${value1}a${value2}b$value2$nullObject" 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "input-arg-app", 3 | srcs = ["InputArgApp.scala"], 4 | main_class = "com.komanov.stringformat.bin.InputArgApp", 5 | deps = ["//src/com/komanov/stringformat"], 6 | ) 7 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/bin/InputArgApp.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.bin 2 | 3 | import com.komanov.stringformat.{InputArg, JavaFormats} 4 | 5 | object InputArgApp extends App { 6 | 7 | for (a <- InputArg.values()) { 8 | println(s"$a: ${JavaFormats.concat(a.value1, a.value2, null).length}") 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/stringformat", 6 | "//src/com/komanov/stringformat/macros", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/jmh/Benchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.jmh 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import com.komanov.stringformat.{InputArg, JavaFormats, ScalaFormats} 6 | import org.openjdk.jmh.annotations._ 7 | 8 | @State(Scope.Benchmark) 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 11 | @Fork(value = 2, jvmArgs = Array("-Xmx2G")) 12 | @Measurement(iterations = 7, time = 3, timeUnit = TimeUnit.SECONDS) 13 | @Warmup(iterations = 3, time = 3, timeUnit = TimeUnit.SECONDS) 14 | abstract class BenchmarkBase 15 | 16 | @State(Scope.Benchmark) 17 | class ManyParamsBenchmark extends BenchmarkBase { 18 | 19 | @Param 20 | var arg: InputArg = InputArg.Tiny 21 | 22 | var nullObject: Object = null 23 | 24 | @Benchmark 25 | def javaConcat(): String = { 26 | JavaFormats.concat(arg.value1, arg.value2, nullObject) 27 | } 28 | 29 | @Benchmark 30 | def scalaConcat(): String = { 31 | ScalaFormats.concat(arg.value1, arg.value2, nullObject) 32 | } 33 | 34 | @Benchmark 35 | def stringFormat(): String = { 36 | JavaFormats.stringFormat(arg.value1, arg.value2, nullObject) 37 | } 38 | 39 | @Benchmark 40 | def messageFormat(): String = { 41 | JavaFormats.messageFormat(arg.value1, arg.value2, nullObject) 42 | } 43 | 44 | @Benchmark 45 | def slf4j(): String = { 46 | JavaFormats.slf4j(arg.value1, arg.value2, nullObject) 47 | } 48 | 49 | @Benchmark 50 | def concatOptimized1(): String = { 51 | ScalaFormats.optimizedConcat1(arg.value1, arg.value2, nullObject) 52 | } 53 | 54 | @Benchmark 55 | def concatOptimized2(): String = { 56 | ScalaFormats.optimizedConcat2(arg.value1, arg.value2, nullObject) 57 | } 58 | 59 | @Benchmark 60 | def concatOptimizedMacros(): String = { 61 | ScalaFormats.optimizedConcatMacros(arg.value1, arg.value2, nullObject) 62 | } 63 | 64 | @Benchmark 65 | def sInterpolator(): String = { 66 | ScalaFormats.sInterpolator(arg.value1, arg.value2, nullObject) 67 | } 68 | 69 | @Benchmark 70 | def fInterpolator(): String = { 71 | ScalaFormats.fInterpolator(arg.value1, arg.value2, nullObject) 72 | } 73 | 74 | @Benchmark 75 | def rawInterpolator(): String = { 76 | ScalaFormats.rawInterpolator(arg.value1, arg.value2, nullObject) 77 | } 78 | 79 | @Benchmark 80 | def sfiInterpolator(): String = { 81 | ScalaFormats.sfiInterpolator(arg.value1, arg.value2, nullObject) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/jmh/NewStringBenchmark.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.jmh 2 | 3 | import com.komanov.stringformat.FastStringFactory 4 | import org.openjdk.jmh.annotations.Benchmark 5 | 6 | object NewStringBenchmarkData { 7 | val chars = new Array[Char](1006) 8 | val sb = new java.lang.StringBuilder(chars.length) 9 | .append(chars) 10 | val (bytes, coder) = (sb.toString.getBytes, 0.toByte) 11 | } 12 | 13 | class NewStringBenchmark extends BenchmarkBase { 14 | 15 | @Benchmark 16 | def baseline: String = { 17 | "" 18 | } 19 | 20 | @Benchmark 21 | def newString: String = { 22 | new String(NewStringBenchmarkData.chars) 23 | } 24 | 25 | @Benchmark 26 | def fastString: String = { 27 | FastStringFactory.fastNewString(NewStringBenchmarkData.bytes, NewStringBenchmarkData.coder) 28 | } 29 | 30 | @Benchmark 31 | def sbToString: String = { 32 | NewStringBenchmarkData.sb.toString 33 | } 34 | 35 | @Benchmark 36 | def fastSb: String = { 37 | FastStringFactory.fastNewString(NewStringBenchmarkData.sb) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/jmh/SimpleBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.jmh 2 | 3 | import org.openjdk.jmh.annotations.Benchmark 4 | 5 | class EmptyStringBenchmark extends BenchmarkBase { 6 | 7 | @Benchmark 8 | def baseline: String = { 9 | "" 10 | } 11 | 12 | @Benchmark 13 | def sInterpolator: String = { 14 | s"" 15 | } 16 | 17 | @Benchmark 18 | def sfiInterpolator: String = { 19 | import com.komanov.stringformat.macros.MacroConcat._ 20 | sfi"" 21 | } 22 | } 23 | 24 | class ConstStringBenchmark extends BenchmarkBase { 25 | 26 | @Benchmark 27 | def baseline: String = { 28 | "abc" 29 | } 30 | 31 | @Benchmark 32 | def sInterpolator: String = { 33 | s"abc" 34 | } 35 | 36 | @Benchmark 37 | def sfiInterpolator: String = { 38 | import com.komanov.stringformat.macros.MacroConcat._ 39 | sfi"abc" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/jmh/StringBuilderNewStringBenchmark.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.jmh 2 | 3 | import com.komanov.stringformat.FastStringFactory 4 | import org.openjdk.jmh.annotations.Benchmark 5 | 6 | class StringBuilderNewStringBenchmark extends BenchmarkBase { 7 | 8 | @Benchmark 9 | def notOptimal: String = { 10 | new java.lang.StringBuilder(33) 11 | .append("0123456789") 12 | .append(1) 13 | .append("0123456789") 14 | .append(1) 15 | .append("0123456789") 16 | .append(1) 17 | .toString 18 | } 19 | 20 | @Benchmark 21 | def optimal: String = { 22 | val sb = new java.lang.StringBuilder(33) 23 | .append("0123456789") 24 | .append(1) 25 | .append("0123456789") 26 | .append(1) 27 | .append("0123456789") 28 | .append(1) 29 | FastStringFactory.fastNewString(sb) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/macros/BUILD: -------------------------------------------------------------------------------- 1 | scala_macro_library( 2 | name = "macros", 3 | srcs = ["MacroConcat.scala"], 4 | visibility = ["//visibility:public"], 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/macros/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/stringformat/macros", 6 | "@maven//:org_mockito_mockito_core", 7 | "@maven//:org_specs2_specs2_mock_2_13", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | jvm_flags = [ 5 | "--add-opens java.base/java.lang=ALL-UNNAMED", 6 | ], 7 | runtime_deps = [ 8 | "@maven//:org_slf4j_slf4j_api", 9 | ], 10 | deps = [ 11 | "//src/com/komanov/stringformat", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/tests/FormatsTest.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.tests 2 | 3 | import com.komanov.stringformat.{JavaFormats, ScalaFormats, InputArg} 4 | 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | import org.specs2.specification.core.Fragment 7 | 8 | class FormatsTest extends SpecificationWithJUnit { 9 | 10 | val formats: List[(String, (Int, String, Object) => String)] = List( 11 | "javaConcat" -> JavaFormats.concat, 12 | "stringFormat" -> JavaFormats.stringFormat, 13 | "messageFormat" -> JavaFormats.messageFormat, 14 | "slf4j" -> JavaFormats.slf4j, 15 | "scalaConcat" -> ScalaFormats.concat, 16 | "optimizedConcat1" -> ScalaFormats.optimizedConcat1, 17 | "optimizedConcat2" -> ScalaFormats.optimizedConcat2, 18 | "optimizedConcatMacros" -> ScalaFormats.optimizedConcatMacros, 19 | "sInterpolator" -> ScalaFormats.sInterpolator, 20 | "fInterpolator" -> ScalaFormats.fInterpolator, 21 | "rawInterpolator" -> ScalaFormats.rawInterpolator, 22 | "sfiInterpolator" -> ScalaFormats.sfiInterpolator 23 | ) 24 | 25 | Fragment.foreach(formats) { case (name, f) => 26 | s"$name" should { 27 | "product the same result as JavaConcat" >> { 28 | f(1, "str", null) must beEqualTo(JavaFormats.concat(1, "str", null)) 29 | f(1, null, "str") must beEqualTo(JavaFormats.concat(1, null, "str")) 30 | } 31 | } 32 | } 33 | 34 | val formatsWithInputArgs = for { 35 | (name, f) <- formats 36 | arg <- InputArg.values 37 | } yield (name, arg, f) 38 | 39 | Fragment.foreach(formatsWithInputArgs) { case (name, arg, f) => 40 | s"$name" should { 41 | s"product the same result as JavaConcat for $arg" >> { 42 | f(arg.value1, arg.value2, null) must beEqualTo(JavaFormats.concat(arg.value1, arg.value2, null)) 43 | } 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/tests/OptimizedConcatenation1Test.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.tests 2 | 3 | import com.komanov.stringformat.OptimizedConcatenation1 4 | 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | 7 | class OptimizedConcatenation1Test extends SpecificationWithJUnit { 8 | 9 | "concat" should { 10 | "objects" >> { 11 | OptimizedConcatenation1.concat(o1, o2) must be_===(s1 + s2) 12 | OptimizedConcatenation1.concat(o1, o2, o3) must be_===(s1 + s2 + s3) 13 | OptimizedConcatenation1.concat(o1, o2, o3, o4) must be_===(s1 + s2 + s3 + s4) 14 | OptimizedConcatenation1.concat(o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5) 15 | OptimizedConcatenation1.concat(o1, o2, o3, o4, o5, o6) must be_===(s1 + s2 + s3 + s4 + s5 + s6) 16 | OptimizedConcatenation1.concatObjects(o1, o2, o3, o4, o5, o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5) 17 | } 18 | 19 | "strings" >> { 20 | OptimizedConcatenation1.concat(s1, s2) must be_===(s1 + s2) 21 | OptimizedConcatenation1.concat(s1, s2, s3) must be_===(s1 + s2 + s3) 22 | OptimizedConcatenation1.concat(s1, s2, s3, s4) must be_===(s1 + s2 + s3 + s4) 23 | OptimizedConcatenation1.concat(s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5) 24 | OptimizedConcatenation1.concat(s1, s2, s3, s4, s5, s6) must be_===(s1 + s2 + s3 + s4 + s5 + s6) 25 | OptimizedConcatenation1.concatStrings(s1, s2, s3, s4, s5, s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5) 26 | } 27 | 28 | "replace null with 'null' string" >> { 29 | OptimizedConcatenation1.concat(null, Int.box(1)) must be_===("null1") 30 | OptimizedConcatenation1.concat(null, "1") must be_===("null1") 31 | } 32 | } 33 | 34 | val o1: Object = Int.box(10000) 35 | val o2: Object = Int.box(200000) 36 | val o3: Object = Int.box(3000000) 37 | val o4: Object = Int.box(40000000) 38 | val o5: Object = Int.box(500000000) 39 | val o6: Object = Int.box(60000000) 40 | val o7: Object = Int.box(7000000) 41 | 42 | val s1 = "10000" 43 | val s2 = "200000" 44 | val s3 = "3000000" 45 | val s4 = "40000000" 46 | val s5 = "500000000" 47 | val s6 = "60000000" 48 | val s7 = "7000000" 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/com/komanov/stringformat/tests/OptimizedConcatenation2Test.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.stringformat.tests 2 | 3 | import com.komanov.stringformat.OptimizedConcatenation2 4 | 5 | import org.specs2.mutable.SpecificationWithJUnit 6 | 7 | class OptimizedConcatenation2Test extends SpecificationWithJUnit { 8 | 9 | "concat" should { 10 | "objects" >> { 11 | OptimizedConcatenation2.concat(o1, o2) must be_===(s1 + s2) 12 | OptimizedConcatenation2.concat(o1, o2, o3) must be_===(s1 + s2 + s3) 13 | OptimizedConcatenation2.concat(o1, o2, o3, o4) must be_===(s1 + s2 + s3 + s4) 14 | OptimizedConcatenation2.concat(o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5) 15 | OptimizedConcatenation2.concat(o1, o2, o3, o4, o5, o6) must be_===(s1 + s2 + s3 + s4 + s5 + s6) 16 | OptimizedConcatenation2.concatObjects(o1, o2, o3, o4, o5, o1, o2, o3, o4, o5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5) 17 | } 18 | 19 | "strings" >> { 20 | OptimizedConcatenation2.concat(s1, s2) must be_===(s1 + s2) 21 | OptimizedConcatenation2.concat(s1, s2, s3) must be_===(s1 + s2 + s3) 22 | OptimizedConcatenation2.concat(s1, s2, s3, s4) must be_===(s1 + s2 + s3 + s4) 23 | OptimizedConcatenation2.concat(s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5) 24 | OptimizedConcatenation2.concat(s1, s2, s3, s4, s5, s6) must be_===(s1 + s2 + s3 + s4 + s5 + s6) 25 | OptimizedConcatenation2.concatStrings(s1, s2, s3, s4, s5, s1, s2, s3, s4, s5) must be_===(s1 + s2 + s3 + s4 + s5 + s1 + s2 + s3 + s4 + s5) 26 | } 27 | 28 | "replace null with 'null' string" >> { 29 | OptimizedConcatenation2.concat(null, Int.box(1)) must be_===("null1") 30 | OptimizedConcatenation2.concat(null, "1") must be_===("null1") 31 | } 32 | } 33 | 34 | val o1: Object = Int.box(10000) 35 | val o2: Object = Int.box(200000) 36 | val o3: Object = Int.box(3000000) 37 | val o4: Object = Int.box(40000000) 38 | val o5: Object = Int.box(500000000) 39 | val o6: Object = Int.box(60000000) 40 | val o7: Object = Int.box(7000000) 41 | 42 | val s1 = "10000" 43 | val s2 = "200000" 44 | val s3 = "3000000" 45 | val s4 = "40000000" 46 | val s5 = "500000000" 47 | val s6 = "60000000" 48 | val s7 = "7000000" 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/com/komanov/translit/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "translit", 3 | srcs = ["RenameTranslitApp.scala"], 4 | main_class = "com.komanov.translit.RenameTranslitApp", 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/BUILD: -------------------------------------------------------------------------------- 1 | scala_library( 2 | name = "uuid", 3 | srcs = glob([ 4 | "*.java", 5 | "*.scala", 6 | ]), 7 | visibility = ["//src/com/komanov/uuid:__subpackages__"], 8 | ) 9 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/DigitResolver.java: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid; 2 | 3 | public class DigitResolver 4 | { 5 | private static final int[] digits = calculateDigits(); 6 | 7 | public static int digit(char ch) 8 | { 9 | return ch >= digits.length ? -1 : digits[ch]; 10 | } 11 | 12 | private static int[] calculateDigits() 13 | { 14 | int[] result = new int[(int) 'f' + 1]; 15 | for (int i = 0; i < result.length; ++i) 16 | { 17 | result[i] = -1; 18 | } 19 | 20 | for (char ch = '0'; ch <= '9'; ++ch) 21 | { 22 | result[ch] = ch - '0'; 23 | } 24 | 25 | for (char ch = 'A'; ch <= 'F'; ++ch) 26 | { 27 | result[ch] = 10 + (ch - 'A'); 28 | } 29 | 30 | for (char ch = 'a'; ch <= 'f'; ++ch) 31 | { 32 | result[ch] = 10 + (ch - 'a'); 33 | } 34 | 35 | return result; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidJava0Utils.java: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid; 2 | 3 | import java.util.UUID; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * Used compiled pattern for splitting. 8 | */ 9 | public class UuidJava0Utils 10 | { 11 | private static final Pattern SPLIT_PATTERN = Pattern.compile("-"); 12 | 13 | public static UUID fromStringFast(String s) 14 | { 15 | String[] components = SPLIT_PATTERN.split(s); 16 | if (components.length != 5) 17 | throw new IllegalArgumentException("Invalid UUID string: " + s); 18 | for (int i = 0; i < 5; i++) 19 | components[i] = "0x" + components[i]; 20 | 21 | long mostSigBits = Long.decode(components[0]).longValue(); 22 | mostSigBits <<= 16; 23 | mostSigBits |= Long.decode(components[1]).longValue(); 24 | mostSigBits <<= 16; 25 | mostSigBits |= Long.decode(components[2]).longValue(); 26 | 27 | long leastSigBits = Long.decode(components[3]).longValue(); 28 | leastSigBits <<= 48; 29 | leastSigBits |= Long.decode(components[4]).longValue(); 30 | 31 | return new UUID(mostSigBits, leastSigBits); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidJava1Utils.java: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * Removed String.split 7 | */ 8 | public class UuidJava1Utils 9 | { 10 | public static UUID fromStringFast(String s) 11 | { 12 | int component1EndIndex = indexOfHyphen(s, 0); 13 | int component2EndIndex = indexOfHyphen(s, component1EndIndex + 1); 14 | int component3EndIndex = indexOfHyphen(s, component2EndIndex + 1); 15 | int component4EndIndex = indexOfHyphen(s, component3EndIndex + 1); 16 | if (s.indexOf('-', component4EndIndex + 1) != -1) 17 | { 18 | throw new IllegalArgumentException("Too much hyphens in a string: " + s); 19 | } 20 | 21 | long mostSigBits = decode(s, 0, component1EndIndex); 22 | mostSigBits <<= 16; 23 | mostSigBits |= decode(s, component1EndIndex + 1, component2EndIndex); 24 | mostSigBits <<= 16; 25 | mostSigBits |= decode(s, component2EndIndex + 1, component3EndIndex); 26 | 27 | long leastSigBits = decode(s, component3EndIndex + 1, component4EndIndex); 28 | leastSigBits <<= 48; 29 | leastSigBits |= decode(s, component4EndIndex + 1, s.length()); 30 | 31 | return new UUID(mostSigBits, leastSigBits); 32 | } 33 | 34 | private static long decode(String s, int from, int to) 35 | { 36 | return Long.decode("0x" + s.substring(from, to)); 37 | } 38 | 39 | private static int indexOfHyphen(String s, int from) 40 | { 41 | int index = s.indexOf('-', from); 42 | if (index == -1) 43 | { 44 | throw new IllegalArgumentException("Expected 4 hyphens (-) in a string: " + s); 45 | } 46 | return index; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidJava2Utils.java: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid; 2 | 3 | import java.util.UUID; 4 | 5 | import static java.lang.Long.parseLong; 6 | 7 | /** 8 | * Removed concatenation (used parseLong instead of decode). 9 | */ 10 | public class UuidJava2Utils 11 | { 12 | public static UUID fromStringFast(String s) 13 | { 14 | int component1EndIndex = indexOfHyphen(s, 0); 15 | int component2EndIndex = indexOfHyphen(s, component1EndIndex + 1); 16 | int component3EndIndex = indexOfHyphen(s, component2EndIndex + 1); 17 | int component4EndIndex = indexOfHyphen(s, component3EndIndex + 1); 18 | if (s.indexOf('-', component4EndIndex + 1) != -1) 19 | { 20 | throw new IllegalArgumentException("Too much hyphens in a string: " + s); 21 | } 22 | 23 | // This is a copy-paste from UUID.fromString implementation 24 | long mostSigBits = substringAndParseLong(s, 0, component1EndIndex); 25 | mostSigBits <<= 16; 26 | mostSigBits |= substringAndParseLong(s, component1EndIndex + 1, component2EndIndex); 27 | mostSigBits <<= 16; 28 | mostSigBits |= substringAndParseLong(s, component2EndIndex + 1, component3EndIndex); 29 | 30 | long leastSigBits = substringAndParseLong(s, component3EndIndex + 1, component4EndIndex); 31 | leastSigBits <<= 48; 32 | leastSigBits |= substringAndParseLong(s, component4EndIndex + 1, s.length()); 33 | 34 | return new UUID(mostSigBits, leastSigBits); 35 | } 36 | 37 | private static long substringAndParseLong(String s, int from, int to) 38 | { 39 | return parseLong(s.substring(from, to), 16); 40 | } 41 | 42 | private static int indexOfHyphen(String s, int from) 43 | { 44 | int index = s.indexOf('-', from); 45 | if (index == -1) 46 | { 47 | throw new IllegalArgumentException("Expected 4 hyphens (-) in a string: " + s); 48 | } 49 | return index; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidJavaEpic2Utils.java: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid; 2 | 3 | import java.util.UUID; 4 | 5 | /* a version by Noam */ 6 | public class UuidJavaEpic2Utils 7 | { 8 | public static UUID fromStringFast(String s) 9 | { 10 | validate(s); 11 | final int component3EndIndex = 18; 12 | final long mostSigBits = parseHex(s, 0, component3EndIndex); 13 | final long leastSigBits = parseHex(s, component3EndIndex + 1, s.length()); 14 | return new UUID(mostSigBits, leastSigBits); 15 | } 16 | 17 | private static void validate(String s) 18 | { 19 | if (s.length() != 36) 20 | { 21 | throw new IllegalArgumentException("Illegal UUID " + s); 22 | } 23 | if (s.charAt(8) != '-' && s.charAt(13) != '-' && s.charAt(18) != '-' && s.charAt(23) != '-') 24 | { 25 | throw new IllegalArgumentException("Expected 4 hyphens (-) in a string: " + s); 26 | } 27 | } 28 | 29 | private static long parseHex(final String s, final int from, final int to) 30 | { 31 | long result = 0; 32 | for (int i = from; i < to; i++) 33 | { 34 | char ch = s.charAt(i); 35 | if (ch == '-') continue; 36 | final int digit = Character.digit(ch, 16); 37 | if (digit < 0) 38 | throw new NumberFormatException(s.substring(from, to)); 39 | result = result * 16 + digit; 40 | } 41 | 42 | return result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidJavaEpicUtils.java: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * Backward incompatible version that allows strictly fixed format which is returned by UUID.toString 7 | */ 8 | public class UuidJavaEpicUtils 9 | { 10 | public static UUID fromStringFast(String s) 11 | { 12 | validate(s); 13 | 14 | int component1EndIndex = 8; 15 | int component2EndIndex = 13; 16 | int component3EndIndex = 18; 17 | int component4EndIndex = 23; 18 | 19 | // This is a copy-paste from UUID.fromString implementation 20 | long mostSigBits = parseHex(s, 0, component1EndIndex); 21 | mostSigBits <<= 16; 22 | mostSigBits |= parseHex(s, component1EndIndex + 1, component2EndIndex); 23 | mostSigBits <<= 16; 24 | mostSigBits |= parseHex(s, component2EndIndex + 1, component3EndIndex); 25 | 26 | long leastSigBits = parseHex(s, component3EndIndex + 1, component4EndIndex); 27 | leastSigBits <<= 48; 28 | leastSigBits |= parseHex(s, component4EndIndex + 1, s.length()); 29 | 30 | return new UUID(mostSigBits, leastSigBits); 31 | } 32 | 33 | private static void validate(String s) 34 | { 35 | if (s.length() != 36) 36 | { 37 | throw new IllegalArgumentException("Illegal UUID " + s); 38 | } 39 | if (s.charAt(8) != '-' && s.charAt(13) != '-' && s.charAt(18) != '-' && s.charAt(23) != '-') 40 | { 41 | throw new IllegalArgumentException("Expected 4 hyphens (-) in a string: " + s); 42 | } 43 | } 44 | 45 | private static long parseHex(final String s, final int from, final int to) 46 | { 47 | long result = 0; 48 | for (int i = from; i < to; i++) 49 | { 50 | // Accumulating negatively avoids surprises near MAX_VALUE 51 | char ch = s.charAt(i); 52 | final int digit = DigitResolver.digit(ch); 53 | if (digit < 0) 54 | { 55 | throw new NumberFormatException(s.substring(from, to)); 56 | } 57 | 58 | result = result * 16 + digit; 59 | } 60 | 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidScala1Utils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid 2 | 3 | import java.lang.Long.parseLong 4 | import java.util.UUID 5 | 6 | object UuidScala1Utils { 7 | 8 | /** 9 | * A fast version of java.util.UUID#fromString. Less memory allocations (in JDK implementation there are 6 redundant 10 | * allocation: array allocation for split and 5 string concatenations "0x" + component[i]). 11 | */ 12 | def fromStringFast(s: String): UUID = { 13 | val component1EndIndex = indexOfHyphen(s, 0) 14 | val component2EndIndex = indexOfHyphen(s, component1EndIndex + 1) 15 | val component3EndIndex = indexOfHyphen(s, component2EndIndex + 1) 16 | val component4EndIndex = indexOfHyphen(s, component3EndIndex + 1) 17 | require(s.indexOf('-', component4EndIndex + 1) == -1, s"Too much hyphens in a string: $s") 18 | 19 | // This is a copy-paste from UUID.fromString implementation 20 | var mostSigBits: Long = parseLong(s.substring(0, component1EndIndex), 16) 21 | mostSigBits <<= 16 22 | mostSigBits |= parseLong(s.substring(component1EndIndex + 1, component2EndIndex), 16) 23 | mostSigBits <<= 16 24 | mostSigBits |= parseLong(s.substring(component2EndIndex + 1, component3EndIndex), 16) 25 | 26 | var leastSigBits: Long = parseLong(s.substring(component3EndIndex + 1, component4EndIndex), 16) 27 | leastSigBits <<= 48 28 | leastSigBits |= parseLong(s.substring(component4EndIndex + 1), 16) 29 | 30 | new UUID(mostSigBits, leastSigBits) 31 | } 32 | 33 | private def indexOfHyphen(s: String, from: Int): Int = { 34 | val index = s.indexOf('-', from) 35 | require(index != -1, s"Expected 4 hyphens (-) in a string: $s") 36 | index 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidScala2Utils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid 2 | 3 | import java.util.UUID 4 | 5 | object UuidScala2Utils { 6 | 7 | def fromStringFast(s: String): UUID = { 8 | val component1EndIndex = indexOfHyphen(s, 0) 9 | val component2EndIndex = indexOfHyphen(s, component1EndIndex + 1) 10 | val component3EndIndex = indexOfHyphen(s, component2EndIndex + 1) 11 | val component4EndIndex = indexOfHyphen(s, component3EndIndex + 1) 12 | require(s.indexOf('-', component4EndIndex + 1) == -1, s"Too much hyphens in a string: $s") 13 | 14 | // This is a copy-paste from UUID.fromString implementation 15 | var mostSigBits: Long = parseHex(s, 0, component1EndIndex) 16 | mostSigBits <<= 16 17 | mostSigBits |= parseHex(s, component1EndIndex + 1, component2EndIndex) 18 | mostSigBits <<= 16 19 | mostSigBits |= parseHex(s, component2EndIndex + 1, component3EndIndex) 20 | 21 | var leastSigBits: Long = parseHex(s, component3EndIndex + 1, component4EndIndex) 22 | leastSigBits <<= 48 23 | leastSigBits |= parseHex(s, component4EndIndex + 1, s.length) 24 | 25 | new UUID(mostSigBits, leastSigBits) 26 | } 27 | 28 | private def indexOfHyphen(s: String, from: Int): Int = { 29 | val index = s.indexOf('-', from) 30 | require(index != -1, s"Expected 4 hyphens (-) in a string: $s") 31 | index 32 | } 33 | 34 | private def parseHex(s: String, from: Int, to: Int): Long = { 35 | if (to <= from) { 36 | throw new NumberFormatException(s"An empty component found in $s") 37 | } 38 | if (to - from > 16) { 39 | throw new NumberFormatException(s"Too long component found in $s: ${s.substring(from, to)}") 40 | } 41 | 42 | var i = from 43 | var result = 0L 44 | 45 | while (i < to) { 46 | val ch = s.charAt(i) 47 | i += 1 48 | val digit = Character.digit(ch, 16) 49 | if (digit < 0) { 50 | if (i == from && ch == '+') { 51 | // allow the first + sign for backward compatibility with UUID.fromString 52 | } else { 53 | throw new NumberFormatException(s"Unknown character $ch in $s") 54 | } 55 | } else { 56 | result = result * 16 + digit 57 | } 58 | } 59 | 60 | result 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidScala3Utils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid 2 | 3 | import java.util.UUID 4 | 5 | import scala.annotation.tailrec 6 | 7 | object UuidScala3Utils { 8 | 9 | def fromStringFast(s: String): UUID = { 10 | val component1EndIndex = indexOfHyphen(s, 0) 11 | val component2EndIndex = indexOfHyphen(s, component1EndIndex + 1) 12 | val component3EndIndex = indexOfHyphen(s, component2EndIndex + 1) 13 | val component4EndIndex = indexOfHyphen(s, component3EndIndex + 1) 14 | if (s.indexOf('-', component4EndIndex + 1) != -1) { 15 | throw new IllegalArgumentException(s"Too much hyphens in a string: $s") 16 | } 17 | 18 | // This is a copy-paste from UUID.fromString implementation 19 | var mostSigBits: Long = parseHex(s, 0, component1EndIndex) 20 | mostSigBits <<= 16 21 | mostSigBits |= parseHex(s, component1EndIndex + 1, component2EndIndex) 22 | mostSigBits <<= 16 23 | mostSigBits |= parseHex(s, component2EndIndex + 1, component3EndIndex) 24 | 25 | var leastSigBits: Long = parseHex(s, component3EndIndex + 1, component4EndIndex) 26 | leastSigBits <<= 48 27 | leastSigBits |= parseHex(s, component4EndIndex + 1, s.length) 28 | 29 | new UUID(mostSigBits, leastSigBits) 30 | } 31 | 32 | private def indexOfHyphen(s: String, from: Int): Int = { 33 | val index = s.indexOf('-', from) 34 | if (index == -1) { 35 | throw new IllegalArgumentException(s"Expected 4 hyphens (-) in a string: $s") 36 | } 37 | index 38 | } 39 | 40 | private def parseHex(s: String, from: Int, to: Int): Long = { 41 | if (to <= from) { 42 | throw new NumberFormatException(s"An empty component found in $s") 43 | } 44 | if (to - from > 16) { 45 | throw new NumberFormatException(s"Too long component found in $s: ${s.substring(from, to)}") 46 | } 47 | 48 | parseHexRec(s, from, from, to, 0L) 49 | } 50 | 51 | @tailrec 52 | private def parseHexRec(s: String, i: Int, from: Int, to: Int, prevResult: Long): Long = { 53 | if (i < to) { 54 | val ch = s.charAt(i) 55 | val digit = Character.digit(ch, 16) 56 | if (digit >= 0) { 57 | parseHexRec(s, i + 1, from, to, prevResult * 16 + digit) 58 | } else if (i == from && ch == '+') { 59 | parseHexRec(s, i + 1, from, to, prevResult) 60 | } else { 61 | throw new NumberFormatException(s"Unknown character $ch in $s") 62 | } 63 | } else { 64 | prevResult 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidScala4Utils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid 2 | 3 | import java.util.UUID 4 | 5 | object UuidScala4Utils { 6 | 7 | def fromStringFast(s: String): UUID = { 8 | var component = 0 9 | var most = 0L 10 | val least = s.map({ 11 | case '0' => 0 12 | case '1' => 1 13 | case '2' => 2 14 | case '3' => 3 15 | case '4' => 4 16 | case '5' => 5 17 | case '6' => 6 18 | case '7' => 7 19 | case '8' => 8 20 | case '9' => 9 21 | case 'A' | 'a' => 10 22 | case 'B' | 'b' => 11 23 | case 'C' | 'c' => 12 24 | case 'D' | 'd' => 13 25 | case 'E' | 'e' => 14 26 | case 'F' | 'f' => 15 27 | case '-' => -1 28 | case '+' => 0 29 | case ch => throw new IllegalArgumentException(s"unexpected character $ch in $s") 30 | }).foldLeft(0L) { (result, digit) => { 31 | if (digit == -1) { 32 | component += 1 33 | component match { 34 | case 1 | 2 | 4 => result 35 | case 3 => most = result; 0L 36 | case _ => throw new IllegalArgumentException(s"too many components in $s") 37 | } 38 | } else { 39 | result * 16 + digit 40 | } 41 | } 42 | } 43 | 44 | new UUID(most, least) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidScalaEpicUtils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid 2 | 3 | import java.util.UUID 4 | 5 | object UuidScalaEpicUtils { 6 | 7 | def fromStringFast(s: String): UUID = { 8 | val component1EndIndex = 8 9 | val component2EndIndex = 13 10 | val component3EndIndex = 18 11 | val component4EndIndex = 23 12 | 13 | require(s.length == 36, s"Illegal UUID: $s") 14 | require(s.charAt(component1EndIndex) == '-' && 15 | s.charAt(component2EndIndex) == '-' && 16 | s.charAt(component3EndIndex) == '-' && 17 | s.charAt(component4EndIndex) == '-', s"Expected 4 hyphens (-) in a string: $s") 18 | 19 | var mostSigBits: Long = parseHex(s, 0, component1EndIndex) 20 | mostSigBits <<= 16 21 | mostSigBits |= parseHex(s, component1EndIndex + 1, component2EndIndex) 22 | mostSigBits <<= 16 23 | mostSigBits |= parseHex(s, component2EndIndex + 1, component3EndIndex) 24 | var leastSigBits: Long = parseHex(s, component3EndIndex + 1, component4EndIndex) 25 | leastSigBits <<= 48 26 | leastSigBits |= parseHex(s, component4EndIndex + 1, s.length) 27 | new UUID(mostSigBits, leastSigBits) 28 | } 29 | 30 | private def parseHex(s: String, from: Int, to: Int): Long = { 31 | var i = from 32 | var result = 0L 33 | 34 | while (i < to) { 35 | val ch = s.charAt(i) 36 | i += 1 37 | val digit = DigitResolver.digit(ch) 38 | if (digit < 0) { 39 | throw new NumberFormatException(s"Unknown character $ch in $s") 40 | } 41 | 42 | result = result * 16 + digit 43 | } 44 | 45 | result 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/UuidScalaFinalUtils.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid 2 | 3 | import java.util.UUID 4 | 5 | object UuidScalaFinalUtils { 6 | 7 | def fromStringFast(s: String): UUID = { 8 | val component1EndIndex = indexOfHyphen(s, 0) 9 | val component2EndIndex = indexOfHyphen(s, component1EndIndex + 1) 10 | val component3EndIndex = indexOfHyphen(s, component2EndIndex + 1) 11 | val component4EndIndex = indexOfHyphen(s, component3EndIndex + 1) 12 | require(s.indexOf('-', component4EndIndex + 1) == -1, s"Too much hyphens in a string: $s") 13 | 14 | // This is a copy-paste from UUID.fromString implementation 15 | var mostSigBits: Long = parseHex(s, 0, component1EndIndex) 16 | mostSigBits <<= 16 17 | mostSigBits |= parseHex(s, component1EndIndex + 1, component2EndIndex) 18 | mostSigBits <<= 16 19 | mostSigBits |= parseHex(s, component2EndIndex + 1, component3EndIndex) 20 | 21 | var leastSigBits: Long = parseHex(s, component3EndIndex + 1, component4EndIndex) 22 | leastSigBits <<= 48 23 | leastSigBits |= parseHex(s, component4EndIndex + 1, s.length) 24 | 25 | new UUID(mostSigBits, leastSigBits) 26 | } 27 | 28 | private def indexOfHyphen(s: String, from: Int): Int = { 29 | val index = s.indexOf('-', from) 30 | require(index != -1, s"Expected 4 hyphens (-) in a string: $s") 31 | index 32 | } 33 | 34 | private def parseHex(s: String, from: Int, to: Int): Long = { 35 | if (to <= from) { 36 | throw new NumberFormatException(s"An empty component found in $s") 37 | } 38 | if (to - from > 16) { 39 | throw new NumberFormatException(s"Too long component found in $s: ${s.substring(from, to)}") 40 | } 41 | 42 | var i = from 43 | var result = 0L 44 | 45 | while (i < to) { 46 | val ch = s.charAt(i) 47 | i += 1 48 | val digit = DigitResolver.digit(ch) 49 | if (digit < 0) { 50 | if (i == from && ch == '+') { 51 | // allow the first + sign for backward compatibility with UUID.fromString 52 | } else { 53 | throw new NumberFormatException(s"Unknown character $ch in $s") 54 | } 55 | } else { 56 | result = result * 16 + digit 57 | } 58 | } 59 | 60 | result 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/bin/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "bin", 3 | srcs = [ 4 | "GuidParserPerformanceTest.scala", 5 | "UuidTest.java", 6 | ], 7 | main_class = "com.komanov.uuid.bin.GuidParserPerformanceTest", 8 | deps = [ 9 | "//src/com/komanov/uuid", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/gen/BUILD: -------------------------------------------------------------------------------- 1 | scala_binary( 2 | name = "gen", 3 | srcs = ["UuidGen.scala"], 4 | main_class = "com.komanov.uuid.gen.UuidGen", 5 | ) 6 | -------------------------------------------------------------------------------- /src/com/komanov/uuid/gen/UuidGen.scala: -------------------------------------------------------------------------------- 1 | package com.komanov.uuid.gen 2 | 3 | import java.util.UUID 4 | 5 | // uuidgen in linux is very slow (~13 millis per single uuid). 6 | object UuidGen extends App { 7 | 8 | val n = args.headOption 9 | .flatMap(_.toIntOption) 10 | .filter(_ > 0) 11 | .getOrElse(throw new IllegalArgumentException("Expected positive number argument")) 12 | 13 | for (l <- (1 to n).grouped(1000)) { 14 | // reducing IO via grouping 15 | println(l.map(_ => UUID.randomUUID).mkString("\n")) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/komanov/ver/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//src/com/komanov/ver:__subpackages__"]) 2 | 3 | scala_library( 4 | name = "ver", 5 | srcs = [ 6 | "ParseVersionNoAllocUtil.java", 7 | "ParseVersionUtil.java", 8 | "Version.scala", 9 | "VersionNoAlloc.scala", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /src/com/komanov/ver/README.md: -------------------------------------------------------------------------------- 1 | ## Simple Version String Parsing 2 | 3 | A demonstration how to optimize parsing of simple version string (eg. `1.0.0`), step by step. 4 | 5 | ### Running benchmarks 6 | 7 | JDK8 doesn't have method `Integer.parseUnsignedInt`, so we don't need to run it (also, JDK8 is very old!). 8 | 9 | ```sh 10 | DISABLE_JDK8=1 JDK_JMH_EXTRA="-prof gc" scripts/run-jmh.sh //src/com/komanov/ver/jmh:jmh version-parsing 11 | ``` 12 | 13 | ### JOL 14 | 15 | ```sh 16 | java -jar ~/dloads/jol-cli-0.17-full.jar internals scala.Some com.komanov.ver.Version -cp bazel-bin/src/com/komanov/str/jmh/jmh_deploy.jar:bazel-stuff/bazel-out/k8-fastbuild/bin/src/com/komanov/ver/_scalac/ver/classes 17 | ``` 18 | -------------------------------------------------------------------------------- /src/com/komanov/ver/jmh/BUILD: -------------------------------------------------------------------------------- 1 | scala_benchmark_jmh( 2 | name = "jmh", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/ver", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/com/komanov/ver/tests/BUILD: -------------------------------------------------------------------------------- 1 | scala_specs2_junit( 2 | name = "tests", 3 | srcs = glob(["*.scala"]), 4 | deps = [ 5 | "//src/com/komanov/ver", 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /third_party/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkomanov/stuff/e8602ee396d2a930f22bb3e976de4f603ab36154/third_party/BUILD -------------------------------------------------------------------------------- /tools/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_scala//scala:scala_toolchain.bzl", "scala_toolchain") 2 | 3 | scala_toolchain( 4 | name = "default_scala_toolchain_impl", 5 | scalacopts = [ 6 | #"-Ywarn-unused", 7 | "-deprecation", 8 | "-explaintypes", 9 | #"-Xmacro-settings:print-serializers", //to log sources of serializaer which are generated by kryo-macros 10 | #"-Xmacro-settings:print-codecs", //to log sources of codecs which are generated by jsoniter-scala 11 | 12 | ], 13 | visibility = ["//visibility:public"], 14 | ) 15 | 16 | toolchain( 17 | name = "default_scala_toolchain", 18 | toolchain = "default_scala_toolchain_impl", 19 | toolchain_type = "@io_bazel_rules_scala//scala:toolchain_type", 20 | visibility = ["//visibility:public"], 21 | ) 22 | -------------------------------------------------------------------------------- /tools/build_rules/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkomanov/stuff/e8602ee396d2a930f22bb3e976de4f603ab36154/tools/build_rules/BUILD -------------------------------------------------------------------------------- /tools/build_rules/prelude_bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library", "scala_macro_library", "scala_binary", "scala_specs2_junit_test") 2 | load("@io_bazel_rules_scala//jmh:jmh.bzl", "scala_benchmark_jmh") 3 | load("@//tools/scala_specs2_junit:scala_specs2_junit.bzl", "scala_specs2_junit") 4 | -------------------------------------------------------------------------------- /tools/scala_specs2_junit/BUILD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dkomanov/stuff/e8602ee396d2a930f22bb3e976de4f603ab36154/tools/scala_specs2_junit/BUILD -------------------------------------------------------------------------------- /tools/scala_specs2_junit/scala_specs2_junit.bzl: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_scala//scala:scala.bzl", "scala_specs2_junit_test") 2 | 3 | def scala_specs2_junit( 4 | name, 5 | srcs, 6 | deps = [], 7 | runtime_deps = [], 8 | size = "small", 9 | **kwargs): 10 | scala_specs2_junit_test( 11 | name = name, 12 | srcs = srcs, 13 | deps = deps, 14 | runtime_deps = runtime_deps, 15 | size = size, 16 | suffixes = ["Test"], 17 | **kwargs 18 | ) 19 | --------------------------------------------------------------------------------