-1

I've recently come across an intriguing situation that I'm hoping to get some clarity on. I've written two versions of a program: one in Kotlin and the other in Java. Despite their functional equivalence, I've noticed a discrepancy in their execution times – the Kotlin version seems to be running noticeably slower than the Java version.

The code below is a solution for a Leetcode problem

Java Code:

import java.util.HashMap;
import java.util.Map;

public class Solution {
    public static void main(String[] args) {
        int array[] = {2, 7, 11, 15};
        int target = 9;
        long start = System.nanoTime();
        int[] result = twoSum(array, target);
        for (int i : result) {
            System.out.print(i + " ");
        }
        long end = System.nanoTime();
        System.out.println("\nTime taken =" + (end - start) / 1000);
    }

    public static int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], i);
        }

        for (int i = 0; i < nums.length; i++) {
            int a = nums[i];
            if (map.containsKey(target - a) && map.get(target - a) != i) {
                return new int[]{i, map.get(target - a)};
            }
        }
        return new int[]{};
    }
}

Kotlin code:

import kotlin.system.measureNanoTime

fun main() {
    val array = intArrayOf(2, 7, 11, 15)
    val target = 9
    var result: IntArray
    var time = measureNanoTime {
        result = twoSum(array, target)
        result.forEach {
            print("$it ")
        }
    }
    time /= 1000
    println("\nTime taken =$time")
}

fun twoSum(nums: IntArray, target: Int): IntArray {
    val map = HashMap<Int, Int>()
    for (i in nums.indices) {
        map[nums[i]] = i
    }
    for (i in nums.indices) {
        val b = target - nums[i]
        if (map.containsKey(b)) {
            val ind = map[b]!!
            if (i != ind)
                return intArrayOf(i, ind)
        }
    }
    return intArrayOf(0, 0)
}

The average time taken by Java code is 415 micro-seconds, whereas the time taken by Kotlin code is 3185 micro-seconds. The time taken by Kotlin is more than 7.5 times the time taken by the functionally same Java code, which is way too much!

I'm reaching out to the community to see if anyone else has encountered a similar situation or might have insights into the underlying factors contributing to this discrepancy. This post aims to gather insights from the community regarding the factors that might contribute to such variations. Could differences in bytecode generation, compiler optimizations, or perhaps certain language features be influencing this outcome?

JVM version: 1.8
Kotlin compiler version: 1.8.21

[Note: The average time is calculated by running both, Java and Kotlin code, multiple times on the same machine.]

Goutham
  • 156
  • 13
PriyanshuG
  • 141
  • 1
  • 2
  • 11
  • 7
    I would definitely not trust these numbers without having a [correct benchmark](https://stackoverflow.com/q/504103/869736), without which these numbers seem likely to be complete lies. Among other things, a correct benchmark would ensure everything is properly loaded; these operations should be running thousands of times faster in a properly warmed JVM. tl;dr: use JMH. – Louis Wasserman Aug 22 '23 at 18:07
  • Using Java 20.0.1, Kotlin 1.9.0, and JMH 1.36, I got `0.046 ± 0.001 us/op` for the Java code and `0.060 ± 0.001 us/op` for the Kotlin code. – Slaw Aug 22 '23 at 18:20
  • Matching your Java and Kotlin versions (1.8.0_382 and 1.8.21, respectively), I got the same results. – Slaw Aug 22 '23 at 18:29
  • You most probably measure the class loading of `ArrayKt` class, where extensions like `forEach()` and `indices` are located. As others said, benchmarking this way provides meaningless results. – broot Aug 22 '23 at 18:39
  • Is it me or in the java version, the default return value is an empty array `return new int[] {}`, but in Kotlin, an array with two elements is returned: `intArrayOf(0, 0)` ? It does not look like much, but in the first case, the vm could avoid any allocation, not in the second. If the loop trigger that return statement often, that could explain some perf difference. – amanin Aug 23 '23 at 08:52
  • @amanin Yeah I missed adding 0,0 in the Java version but even after adding it, the results are identical. It seems to be the issue with benchmarking as pointed by others. – PriyanshuG Aug 23 '23 at 10:31
  • 1
    Side notes: do not use the legacy array syntax like `int array[];` Further, if you care about performance, don’t perform three identical hash lookups in a row (mind the DRY principle). Instead of `if(map.containsKey(target - a) && map.get(target - a) != i) { return new int[]{i, map.get(target - a)}; }` use `Integer value = map.get(target - a); if(value != null && value != i) { return new int[] { i, value }; }` – Holger Aug 23 '23 at 11:49

0 Answers0