Cross‑Platform Language Benchmark and Interoperability Analysis
The study benchmarks Kotlin, JavaScript, Dart, C++ and Swift across iOS, Android and HarmonyOS using 237 protobuf files, finding Kotlin’s performance and binary size rival native Swift, JavaScript slower with higher memory, while C++/Rust excel but need more effort, leading to a recommendation of Kotlin Multiplatform with a three‑repo structure for optimal reuse and maintainability.
This article presents a comprehensive benchmark of several cross‑platform languages (Kotlin, JavaScript, Dart, C++, Swift) on three mobile platforms: iOS, Android, and HarmonyOS. The evaluation focuses on execution efficiency, application size before and after adding test cases, peak memory usage, and memory overhead.
The test set consists of 237 protobuf files (total size 2.2 MB) collected from Bilibili’s production environment. Each protobuf is serialized and deserialized to form the test case.
Test Process
1. Build a minimal test project for each language on each platform, providing a UI to trigger the test case and collect statistics. 2. Generate language‑specific protobuf code using custom protoc plugins (one plugin per language). The generated code iterates over all messages, creates an instance, serializes it to a byte array, and deserializes it back.
Example C++ test module:
// fission.proto corresponding C++ test module
namespace bilibili_::account_::fission_::v1_::fission_::proto {
template
inline void TestMessage() {
auto str = Message().SerializeAsString();
auto msg = Message().ParseFromString(str);
}
inline void Test() {
TestMessage<::bilibili::account::fission::v1::EntranceReq>();
TestMessage<::bilibili::account::fission::v1::EntranceReply>();
TestMessage<::bilibili::account::fission::v1::AnimateIcon>();
TestMessage<::bilibili::account::fission::v1::WindowReq>();
TestMessage<::bilibili::account::fission::v1::WindowReply>();
TestMessage<::bilibili::account::fission::v1::PrivacyReq>();
TestMessage<::bilibili::account::fission::v1::PrivacyReply>();
}
}Example Kotlin test module:
// fission.proto corresponding Kotlin test module
@file:OptIn(ExperimentalSerializationApi::class)
package bilibili_account_fission_v1_fission
import kotlinx.serialization.*
import kotlinx.serialization.protobuf.*
import com.bapis.bilibili.account.fission.v1.*
fun doTest() {
ProtoBuf.decodeFromByteArray
(ProtoBuf.encodeToByteArray(KEntranceReq()))
ProtoBuf.decodeFromByteArray
(ProtoBuf.encodeToByteArray(KEntranceReply()))
ProtoBuf.decodeFromByteArray
(ProtoBuf.encodeToByteArray(KAnimateIcon()))
ProtoBuf.decodeFromByteArray
(ProtoBuf.encodeToByteArray(KWindowReq()))
ProtoBuf.decodeFromByteArray
(ProtoBuf.encodeToByteArray(KWindowReply()))
ProtoBuf.decodeFromByteArray
(ProtoBuf.encodeToByteArray(KPrivacyReq()))
ProtoBuf.decodeFromByteArray
(ProtoBuf.encodeToByteArray(KPrivacyReply()))
}Example Swift test module:
import Foundation
import SwiftProtobuf
fileprivate func testMessage
(type: T.Type) {
let message = T()
do {
let encoded = try message.serializedData()
let decoded = try T(serializedData: encoded)
} catch {
print("Error: \(error)")
}
}
func testBilibiliPmmsV1PmmsProto() {
testMessage(type: Bilibili_Pmms_V1_GetPullMessagesReq.self)
testMessage(type: Bilibili_Pmms_V1_GetPullMessagesResponse.self)
testMessage(type: Bilibili_Pmms_V1_Position.self)
testMessage(type: Bilibili_Pmms_V1_ControlParams.self)
testMessage(type: Bilibili_Pmms_V1_Message.self)
}Compilation and execution steps differ per platform: C++ uses -Os -fvisibility=hidden -fvisibility-inlines-hidden -dead_strip; other languages use default Release builds. Dart tests run via Flutter AOT; JavaScript on iOS uses JavaScriptCore, on HarmonyOS uses ArkRuntime.
Results (illustrated in the original article) show that Kotlin’s performance and binary size are comparable to native Swift on iOS and to Java on Android. JavaScript exhibits lower execution speed and higher memory footprint due to its garbage collector. C++ and Rust achieve the best size and speed but require more engineering effort for interop.
Interoperability Discussion
The article classifies languages into two groups: those without an independent runtime (C++, Rust, Kotlin) and those with a runtime (JavaScript, Dart). For the former, interop relies on compiler/linker capabilities and platform bindings (e.g., extern "C" for iOS, JNI for Android, napi for HarmonyOS). For the latter, interop is provided by the language runtime’s bridge mechanisms (e.g., QuickJS, JSCore, ArkRuntime) and method‑channel patterns for Dart.
Key observations include:
Bidirectional interop is essential for core modules (playback, networking, rendering) that need stable, low‑level APIs.
Kotlin Multiplatform (KMP) offers native interop on all three platforms, making it the preferred choice for Bilibili’s business‑logic layer.
Performance measurements confirm that Kotlin’s runtime overhead is acceptable for core modules, while Dart’s AOT performance is sufficient when used within Flutter.
Community support, tooling, and modern language features (coroutines, async/await) further justify Kotlin’s selection.
The article also explores repository strategies (One repo, Two repo, Three repo) for managing cross‑platform codebases, ultimately recommending a “Three repo” approach (separate monorepos for Android, iOS, HarmonyOS, plus a shared KMP repo) to balance reuse and tool‑chain complexity.
Finally, the author reflects on broader topics such as the trade‑off between code reuse and communication overhead, the impact of economic cycles on team structure, and future directions like cross‑platform UI and dynamic configuration.
References and further reading are listed at the end of the original document.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.