diff --git a/kotlin-native/performance/ring/src/main/kotlin/main.kt b/kotlin-native/performance/ring/src/main/kotlin/main.kt index 7fdffafcc8f..30651c68c44 100644 --- a/kotlin-native/performance/ring/src/main/kotlin/main.kt +++ b/kotlin-native/performance/ring/src/main/kotlin/main.kt @@ -229,6 +229,9 @@ class RingLauncher : Launcher() { "Calls.interfaceMethodHexamorphic" to BenchmarkEntryWithInit.create(::CallsBenchmark, { interfaceMethodCall_HexamorphicCallsite() }), "LocalObjects.localArray" to BenchmarkEntryWithInit.create(::LocalObjectsBenchmark, { localArray() }), "ComplexArrays.outerProduct" to BenchmarkEntryWithInit.create(::ComplexArraysBenchmark, { outerProduct() }), + "WeakRefBenchmark.aliveReference" to BenchmarkEntryWithInit.create(::WeakRefBenchmark, { aliveReference() }), + "WeakRefBenchmark.deadReference" to BenchmarkEntryWithInit.create(::WeakRefBenchmark, { deadReference() }), + "WeakRefBenchmark.dyingReference" to BenchmarkEntryWithInit.create(::WeakRefBenchmark, { dyingReference() }), ) init { diff --git a/kotlin-native/performance/ring/src/main/kotlin/org/jetbrains/ring/WeakRefBenchmark.kt b/kotlin-native/performance/ring/src/main/kotlin/org/jetbrains/ring/WeakRefBenchmark.kt new file mode 100644 index 00000000000..52508cec835 --- /dev/null +++ b/kotlin-native/performance/ring/src/main/kotlin/org/jetbrains/ring/WeakRefBenchmark.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.ring + +import kotlin.native.internal.GC +import kotlin.native.ref.WeakReference +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlinx.cinterop.StableRef +import org.jetbrains.benchmarksLauncher.Blackhole +import org.jetbrains.benchmarksLauncher.Random + +private const val REPEAT_COUNT = BENCHMARK_SIZE +private const val REFERENCES_COUNT = 3 + +private class Data(var x: Int = Random.nextInt(1000) + 1) + +private class ReferenceWrapper private constructor( + data: Data +) { + private val weak = WeakReference(data) + private val strong = StableRef.create(data) + + val value: Int + get() { + val ref: Data? = weak.value + if (ref == null) { + return 0 + } + return ref.x + } + + fun dispose() { + strong.dispose() + } + + companion object { + fun create() = ReferenceWrapper(Data()) + } +} + +private fun ReferenceWrapper.stress() = (1..REPEAT_COUNT).sumOf { + this.value +} + +open class WeakRefBenchmark { + private val aliveRef = ReferenceWrapper.create() + private val deadRef = ReferenceWrapper.create().apply { + dispose() + GC.collect() + } + + // Access alive reference. + fun aliveReference() { + assertNotEquals(0, aliveRef.stress()) + } + + // Access dead reference. + fun deadReference() { + assertEquals(0, deadRef.stress()) + } + + // Access reference that is nulled out in the middle. + fun dyingReference() { + val ref = ReferenceWrapper.create() + + ref.dispose() + GC.schedule() + + Blackhole.consume(ref.stress()) + } +} diff --git a/kotlin-native/performance/swiftinterop/build.gradle.kts b/kotlin-native/performance/swiftinterop/build.gradle.kts index 4640da39fc8..351bfe37c11 100644 --- a/kotlin-native/performance/swiftinterop/build.gradle.kts +++ b/kotlin-native/performance/swiftinterop/build.gradle.kts @@ -17,7 +17,11 @@ swiftBenchmark { applicationName = "swiftInterop" commonSrcDirs = listOf("$toolsPath/benchmarks/shared/src/main/kotlin/report", "src", "../shared/src/main/kotlin") nativeSrcDirs = listOf("../shared/src/main/kotlin-native/common", "../shared/src/main/kotlin-native/posix") - swiftSources = listOf("$projectDir/swiftSrc/benchmarks.swift", "$projectDir/swiftSrc/main.swift") + swiftSources = listOf( + "$projectDir/swiftSrc/benchmarks.swift", + "$projectDir/swiftSrc/main.swift", + "$projectDir/swiftSrc/weakRefBenchmarks.swift", + ) compileTasks = listOf("compileKotlinNative", "linkBenchmark${buildType.name.toLowerCase().capitalize()}FrameworkNative") cleanBeforeRunTask = "compileKotlinNative" -} \ No newline at end of file +} diff --git a/kotlin-native/performance/swiftinterop/src/main/kotlin/org/jetbrains/GC.kt b/kotlin-native/performance/swiftinterop/src/main/kotlin/org/jetbrains/GC.kt new file mode 100644 index 00000000000..053d196411f --- /dev/null +++ b/kotlin-native/performance/swiftinterop/src/main/kotlin/org/jetbrains/GC.kt @@ -0,0 +1,11 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.gc + +import kotlin.native.internal.GC + +fun collect() = GC.collect() +fun schedule() = GC.schedule() diff --git a/kotlin-native/performance/swiftinterop/src/main/kotlin/org/jetbrains/model/Data.kt b/kotlin-native/performance/swiftinterop/src/main/kotlin/org/jetbrains/model/Data.kt new file mode 100644 index 00000000000..c0477345707 --- /dev/null +++ b/kotlin-native/performance/swiftinterop/src/main/kotlin/org/jetbrains/model/Data.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.model + +import org.jetbrains.benchmarksLauncher.Random + +class KotlinData(var x: Int = Random.nextInt()) diff --git a/kotlin-native/performance/swiftinterop/swiftSrc/main.swift b/kotlin-native/performance/swiftinterop/swiftSrc/main.swift index 08ca4905ede..6481cbf6c76 100644 --- a/kotlin-native/performance/swiftinterop/swiftSrc/main.swift +++ b/kotlin-native/performance/swiftinterop/swiftSrc/main.swift @@ -36,6 +36,28 @@ swiftLauncher.addBase(name: "createMultigraphOfInt", benchmark: companion.create lambda: { ($0 as! SwiftInteropBenchmarks).stringInterop() })) swiftLauncher.addBase(name: "simpleFunction", benchmark: companion.create(ctor: { return SwiftInteropBenchmarks() }, lambda: { ($0 as! SwiftInteropBenchmarks).simpleFunction() })) +swiftLauncher.addBase( + name: "WeakRefBenchmark.aliveReference", + benchmark: BenchmarkEntryWithInit.companion.create( + ctor: { return WeakRefBenchmark() }, + lambda: { ($0 as! WeakRefBenchmark).aliveReference() } + ) +) +swiftLauncher.addBase( + name: "WeakRefBenchmark.deadReference", + benchmark: BenchmarkEntryWithInit.companion.create( + ctor: { return WeakRefBenchmark() }, + lambda: { ($0 as! WeakRefBenchmark).deadReference() } + ) +) +swiftLauncher.addBase( + name: "WeakRefBenchmark.dyingReference", + benchmark: BenchmarkEntryWithInit.companion.create( + ctor: { return WeakRefBenchmark() }, + lambda: { ($0 as! WeakRefBenchmark).dyingReference() } + ) +) + runner.runBenchmarks(args: args, run: { (arguments: BenchmarkArguments) -> [BenchmarkResult] in if arguments is BaseBenchmarkArguments { let argumentsList: BaseBenchmarkArguments = arguments as! BaseBenchmarkArguments @@ -50,4 +72,4 @@ runner.runBenchmarks(args: args, run: { (arguments: BenchmarkArguments) -> [Benc return runner.parse(args: args, benchmarksListAction: { (baseOnly: KotlinBoolean) in swiftLauncher.benchmarksListAction(baseOnly: baseOnly.boolValue) }) }, collect: { (benchmarks: [BenchmarkResult], arguments: BenchmarkArguments) -> Void in runner.collect(results: benchmarks, arguments: arguments) -}, benchmarksListAction: { (baseOnly: KotlinBoolean) in swiftLauncher.benchmarksListAction(baseOnly: baseOnly.boolValue) }) \ No newline at end of file +}, benchmarksListAction: { (baseOnly: KotlinBoolean) in swiftLauncher.benchmarksListAction(baseOnly: baseOnly.boolValue) }) diff --git a/kotlin-native/performance/swiftinterop/swiftSrc/weakRefBenchmarks.swift b/kotlin-native/performance/swiftinterop/swiftSrc/weakRefBenchmarks.swift new file mode 100644 index 00000000000..3c5f73012bf --- /dev/null +++ b/kotlin-native/performance/swiftinterop/swiftSrc/weakRefBenchmarks.swift @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +import Foundation +import benchmark + +private let REPEAT_COUNT = 10000 +private let REFERENCES_COUNT = 3 + +private class ReferenceWrapper { + private weak var weakRef: KotlinData? + private var strongRef: KotlinData? + + init() { + let data = KotlinData(x: Int32.random(in: 1 ... 1000)) + self.strongRef = data + self.weakRef = data + } + + // This function must not be inlined. We must ensure `weakRef` is accessed at each loop iteration. + @inline(never) func value() -> Int32 { + let ref: KotlinData? = self.weakRef + if ref == nil { + return 0 + } + return ref!.x + } + + func dispose() { + self.strongRef = nil + } + + func stress() -> Int32 { + var counter: Int32 = 0 + for _ in 1...REPEAT_COUNT { + counter += self.value() + } + return counter + } +} + +private func deadReferenceWrapper() -> ReferenceWrapper { + let ref = ReferenceWrapper() + ref.dispose() + GCKt.collect() + return ref +} + +class WeakRefBenchmark { + private let aliveRef = ReferenceWrapper() + private let deadRef = deadReferenceWrapper() + + // Access alive reference. + func aliveReference() { + let counter = aliveRef.stress() + if counter == 0 { + fatalError() + } + } + + // Access dead reference. + func deadReference() { + let counter = deadRef.stress() + if counter != 0 { + fatalError() + } + } + + // Access reference that is nulled out in the middle. + func dyingReference() { + let ref = ReferenceWrapper() + + ref.dispose() + GCKt.schedule() + + let counter = ref.stress() + Blackhole.companion.consume(value: counter) + } +} diff --git a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp index c3264c62063..842cd135116 100644 --- a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp +++ b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.cpp @@ -53,6 +53,12 @@ void gc::ConcurrentMarkAndSweep::ThreadData::SafePointAllocation(size_t size) no gcScheduler_.OnSafePointAllocation(size); mm::SuspendIfRequested(); } + +void gc::ConcurrentMarkAndSweep::ThreadData::Schedule() noexcept { + ThreadStateGuard guard(ThreadState::kNative); + gc_.state_.schedule(); +} + void gc::ConcurrentMarkAndSweep::ThreadData::ScheduleAndWaitFullGC() noexcept { ThreadStateGuard guard(ThreadState::kNative); auto scheduled_epoch = gc_.state_.schedule(); diff --git a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp index ecdef1a9ea4..56d4f586a1d 100644 --- a/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp +++ b/kotlin-native/runtime/src/gc/cms/cpp/ConcurrentMarkAndSweep.hpp @@ -80,6 +80,7 @@ public: void SafePointAllocation(size_t size) noexcept; + void Schedule() noexcept; void ScheduleAndWaitFullGC() noexcept; void ScheduleAndWaitFullGCWithFinalizers() noexcept; diff --git a/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp index 114efb334c9..c5bc342a03d 100644 --- a/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp +++ b/kotlin-native/runtime/src/gc/cms/cpp/GCImpl.cpp @@ -34,6 +34,10 @@ ALWAYS_INLINE void gc::GC::ThreadData::SafePointLoopBody() noexcept { SafePointRegular(*this, GCSchedulerThreadData::kLoopBodyWeight); } +void gc::GC::ThreadData::Schedule() noexcept { + impl_->gc().Schedule(); +} + void gc::GC::ThreadData::ScheduleAndWaitFullGC() noexcept { impl_->gc().ScheduleAndWaitFullGC(); } @@ -124,4 +128,4 @@ ALWAYS_INLINE void gc::GC::processArrayInMark(void* state, ArrayHeader* array) n // static ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept { gc::internal::processFieldInMark(state, field); -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/gc/common/cpp/GC.hpp b/kotlin-native/runtime/src/gc/common/cpp/GC.hpp index 742dcb35fe9..953232d167f 100644 --- a/kotlin-native/runtime/src/gc/common/cpp/GC.hpp +++ b/kotlin-native/runtime/src/gc/common/cpp/GC.hpp @@ -35,6 +35,7 @@ public: void SafePointFunctionPrologue() noexcept; void SafePointLoopBody() noexcept; + void Schedule() noexcept; void ScheduleAndWaitFullGC() noexcept; void ScheduleAndWaitFullGCWithFinalizers() noexcept; diff --git a/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp index dfca326cd84..6dc3051bea4 100644 --- a/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp +++ b/kotlin-native/runtime/src/gc/noop/cpp/GCImpl.cpp @@ -24,6 +24,10 @@ ALWAYS_INLINE void gc::GC::ThreadData::SafePointLoopBody() noexcept { impl_->gc().SafePointLoopBody(); } +void gc::GC::ThreadData::Schedule() noexcept { + impl_->gc().Schedule(); +} + void gc::GC::ThreadData::ScheduleAndWaitFullGC() noexcept { impl_->gc().ScheduleAndWaitFullGC(); } @@ -100,4 +104,4 @@ ALWAYS_INLINE void gc::GC::processObjectInMark(void* state, ObjHeader* object) n ALWAYS_INLINE void gc::GC::processArrayInMark(void* state, ArrayHeader* array) noexcept {} // static -ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {} \ No newline at end of file +ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept {} diff --git a/kotlin-native/runtime/src/gc/noop/cpp/NoOpGC.hpp b/kotlin-native/runtime/src/gc/noop/cpp/NoOpGC.hpp index 5f1bf00677f..7a514c3e782 100644 --- a/kotlin-native/runtime/src/gc/noop/cpp/NoOpGC.hpp +++ b/kotlin-native/runtime/src/gc/noop/cpp/NoOpGC.hpp @@ -41,6 +41,7 @@ public: void SafePointLoopBody() noexcept {} void SafePointAllocation(size_t size) noexcept {} + void Schedule() noexcept {} void ScheduleAndWaitFullGC() noexcept {} void ScheduleAndWaitFullGCWithFinalizers() noexcept {} diff --git a/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp b/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp index fbb33f6c974..dd80b22548a 100644 --- a/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp +++ b/kotlin-native/runtime/src/gc/stms/cpp/GCImpl.cpp @@ -37,6 +37,10 @@ ALWAYS_INLINE void gc::GC::ThreadData::SafePointLoopBody() noexcept { SafePointRegular(*this, GCSchedulerThreadData::kLoopBodyWeight); } +void gc::GC::ThreadData::Schedule() noexcept { + impl_->gc().Schedule(); +} + void gc::GC::ThreadData::ScheduleAndWaitFullGC() noexcept { impl_->gc().ScheduleAndWaitFullGC(); } @@ -119,4 +123,4 @@ ALWAYS_INLINE void gc::GC::processArrayInMark(void* state, ArrayHeader* array) n // static ALWAYS_INLINE void gc::GC::processFieldInMark(void* state, ObjHeader* field) noexcept { gc::internal::processFieldInMark(state, field); -} \ No newline at end of file +} diff --git a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp index 1da48e442b2..7a7fd52b025 100644 --- a/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp +++ b/kotlin-native/runtime/src/gc/stms/cpp/SameThreadMarkAndSweep.hpp @@ -80,6 +80,7 @@ public: void SafePointSlowPath(SafepointFlag flag) noexcept; void SafePointAllocation(size_t size) noexcept; + void Schedule() noexcept { ScheduleAndWaitFullGC(); } void ScheduleAndWaitFullGC() noexcept; void ScheduleAndWaitFullGCWithFinalizers() noexcept { ScheduleAndWaitFullGC(); } diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index e5576c64697..0fcfd03fe55 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -3526,6 +3526,12 @@ void Kotlin_native_internal_GC_collect(KRef) { #endif } +void Kotlin_native_internal_GC_schedule(KRef) { +#if USE_GC + garbageCollect(); +#endif +} + extern "C" void Kotlin_Internal_GC_GCInfoBuilder_Fill(KRef builder, int id) { } diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/GC.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/GC.kt index af4bc537d43..6f367fd061d 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/GC.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/GC.kt @@ -43,6 +43,12 @@ object GC { @GCUnsafeCall("Kotlin_native_internal_GC_collect") external fun collect() + /** + * Trigger new collection without waiting for its completion. + */ + @GCUnsafeCall("Kotlin_native_internal_GC_schedule") + external fun schedule() + /** * Deprecated and unused. * diff --git a/kotlin-native/runtime/src/mm/cpp/Memory.cpp b/kotlin-native/runtime/src/mm/cpp/Memory.cpp index 962e47c2fbf..23620106cac 100644 --- a/kotlin-native/runtime/src/mm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/mm/cpp/Memory.cpp @@ -310,6 +310,12 @@ extern "C" void Kotlin_native_internal_GC_collect(ObjHeader*) { threadData->gc().ScheduleAndWaitFullGCWithFinalizers(); } +extern "C" void Kotlin_native_internal_GC_schedule(ObjHeader*) { + auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData(); + AssertThreadState(threadData, ThreadState::kRunnable); + threadData->gc().Schedule(); +} + extern "C" void Kotlin_native_internal_GC_collectCyclic(ObjHeader*) { // TODO: Remove when legacy MM is gone. // Nothing to do @@ -649,4 +655,4 @@ RUNTIME_NOTHROW ALWAYS_INLINE extern "C" void Kotlin_processFieldInMark(void* st RUNTIME_NOTHROW ALWAYS_INLINE extern "C" void Kotlin_processEmptyObjectInMark(void* state, ObjHeader* object) { // Empty object. Nothing to do. // TODO: Try to generate it in the code generator. -} \ No newline at end of file +}