<@U053Y96LR9U> I was looking at the benchmarks you...
# io
e
@Filipp Zhinkin I was looking at the benchmarks you published. Thanks for that! I wasn't sure why when working on antlr-kotlin I was seeing much worse results with object namespacing, so I cloned and compiled the benchmarks. Looks like for some reason, there is no lazy init of
SomeApi
! See the
SomeApi_instance = new SomeApi();
bit
Copy code
function FunctionCallBenchmark() {
  this.re_1 = 0;
}
protoOf(FunctionCallBenchmark).se = function (bh) {
  // Inline function 'kotlinx.benchmark.Blackhole.consume' call
  var i = freeFunction(0);
  bh.pe(i);
};
protoOf(FunctionCallBenchmark).te = function (bh) {
  // Inline function 'kotlinx.benchmark.Blackhole.consume' call
  var i = SomeApi_instance.callOnObject(0);
  bh.pe(i);
};
function SomeApi() {
}
protoOf(SomeApi).callOnObject = function (x) {
  return imul(x, 2);
};
var SomeApi_instance;
function SomeApi_getInstance() {
  return SomeApi_instance;
}
function freeFunction(x) {
  return imul(x, 2);
}
//region block: init
SomeApi_instance = new SomeApi();
//endregion
//region block: exports
function $jsExportAll$(_) {
  var $org = _.org || (_.org = {});
  var $org$example = $org.example || ($org.example = {});
  defineProp($org$example, 'SomeApi', SomeApi_getInstance);
  $org$example.freeFunction = freeFunction;
}
$jsExportAll$(_);
_.$jsExportAll$ = $jsExportAll$;
_.$_$ = _.$_$ || {};
_.$_$.a = FunctionCallBenchmark;
//endregion
return _;
I did a bunch of tests and I can't come up with a reproducer for lazy init. This is what is supposed to happen:
Copy code
function CharCodes_getInstance() {
  if (CharCodes_instance === VOID)
    new CharCodes();
  return CharCodes_instance;
}
Ok, found! To reproduce the lazy initialization, the object must be marked
internal
. Don't ask me why as this is totally new to me. These are my results now. batchSize: 10000
Copy code
Benchmark                                 Mode  Cnt   Score   Error  Units
FunctionCallBenchmark.freeFunctionCall    avgt   10  10.838 ± 1.161  ns/op
FunctionCallBenchmark.objectFunctionCall  avgt   10  14.302 ± 1.748  ns/op
batchSize: 5000
Copy code
Benchmark                                 Mode  Cnt   Score   Error  Units
FunctionCallBenchmark.freeFunctionCall    avgt   10   9.747 ± 0.518  ns/op
FunctionCallBenchmark.objectFunctionCall  avgt   10  13.119 ± 1.718  ns/op
Those results are in line with what I was seeing in antlr-kotlin. So, since the unsafe object is
public
, we might not incur in the performance penalty.
f
Thanks for digging into the problem! For some reason, locally I don't see any difference when marking an object with
internal
(generated JS code still initializes it eagerly). I'll check what affects lazy initialization
e
@Filipp Zhinkin I investigated again. It wasn't accurate, what really makes the difference is complex properties!
Copy code
object SomeApi {
    val test = IntRange(1, 20)
    fun callOnObject(x: Int): Int = x * 2
}
Note the
val test = IntRange(1, 20)
Property that instantiate a class > lazy init Property with primitive type > no lazy init internal or public doesn't change the result.
thank you color 1
The property visibility doesn't change the result either. It's always lazy
I think this behavior would be useful in the docs, I didn't see it written anywhere.
f
https://kotlinlang.org/docs/object-declarations.html#object-declarations-overview
The initialization of an object declaration is thread-safe and done on first access.
The way "stateless" objects are initialized is an optimization (applied to objects with so called "pure" initializers), while getters with lazy initialization represents default behavior.
e
Thanks! So basically, what is happening for
UnsafeBufferOperations
is an optimization, but is it guaranteed to be always done by the language specification, or some day we'll find out it's not applied anymore?
f
The language specification guarantees the opposite: an object will be initialized only on the first access. However, in some cases, there's no way anyone can observe when an object was initialized (like in case with the
UnsafeBufferOperations
), that's where the optimization kicks in and initializes an object eagerly.
e
Yeah that is what I meant basically, since the optimization in an internal detail, it's not guaranteed it will always be done. But I guess we can take it for granted anyway.
👍 1