Низкая производительность с одновременными командами оболочки в Kotlin по сравнению с Java - PullRequest
0 голосов
/ 19 сентября 2018

У меня странная проблема, в которой я вижу очень низкую производительность кода Kotlin, выполняющего параллельные команды оболочки для извлечения файла.Однако при выполнении одних и тех же команд оболочки с использованием кода Java производительность остается на должном уровне.

Разница ОЧЕНЬ значительна: код Java выполняется за 10-20 секунд, а код Котлина - за 13 минут.

Особый вариант использования - это функция, которая порождает 15 потоков, и все они вызывают эту функцию extract для распаковки одного и того же файла tar.gz.Когда вызывается функция Kotlin, производительность низкая;когда это Java, производительность такая же, как и ожидалось.

Код Kotlin, который я декомпилировал в Java, практически идентичен исполняемому коду Java, с единственным отличием - добавлением Kotlin Intrinsics, который долженне влияет на производительность.

Код Kotlin:

@Synchronized
@Throws(IOException::class)
fun extractTar(tarFile: Path, extractPath: Path) {
    logger.info("Extracting file: " + tarFile.toString())
    logger.info("Destination path: " + extractPath.toString())
    val start = System.currentTimeMillis()
    val untarCommand = Arrays.asList("tar", "-xzf", tarFile.toString(), "-C", extractPath.toString())
    val untarProcess = ProcessBuilder()
            .command(untarCommand)
            .redirectErrorStream(true)
            .start()
    waitAndCheckProcessOutput(untarProcess, "tar -xzf ${tarFile.toString()} -C ${extractPath.toString()}", 15)

    logger.info("Running chmod on folder:" + extractPath.toString())
    val chmodCommand = Arrays.asList("chmod", "-R", "0777", extractPath.toString())
    val chmodProcess = ProcessBuilder()
            .command(chmodCommand)
            .redirectErrorStream(true)
            .start()
    waitAndCheckProcessOutput(chmodProcess, "chmod -R 0777 ${extractPath.toString()}", 15)

    logger.info("Extracted in " + printTime(System.currentTimeMillis() - start))
}

fun waitAndCheckProcessOutput(process: Process, command: String, timeoutInMinutes: Long) {
    try {
        val finished = process.waitFor(timeoutInMinutes, TimeUnit.MINUTES)
        if (!finished) {
            logger.info("Timed out running command $command")
        }
        val output = BufferedReader(InputStreamReader(process.inputStream))
                .lines()
                .collect(Collectors.joining())
        val exitCode = process.exitValue()
        if (exitCode != 0) {
            logger.info("Got exit code " + exitCode + " running command " + command
                    + "\nGot output:\n" + output)
        }
    } catch (e: InterruptedException) {
        logger.info("Interrupted running command $command" + e)
    }
}

fun printTime(timeInMillis: Long): String {
    var duration = Duration.ofMillis(timeInMillis)
    val minutes = duration.toMinutes()
    duration = duration.minusMinutes(minutes)
    val seconds = duration.seconds
    duration = duration.minusSeconds(seconds)
    val millis = duration.toMillis()
    return minutes.toString() + "m " + seconds + "." + millis + "s"
}

Код Kotlin, декомпилированный в Java:

public final synchronized void extractTar(@NotNull Path tarFile, @NotNull Path extractPath) throws IOException {
  Intrinsics.checkParameterIsNotNull(tarFile, "tarFile");
  Intrinsics.checkParameterIsNotNull(extractPath, "extractPath");

  logger.info("Extracting file: " + tarFile.toString());
  logger.info("Destination path: " + extractPath.toString());
  long start = System.currentTimeMillis();
  List untarCommand = Arrays.asList("tar", "-xzf", tarFile.toString(), "-C", extractPath.toString());
  Process untarProcess = (new ProcessBuilder(new String[0]))
      .command(untarCommand)
      .redirectErrorStream(true)
      .start();
  Intrinsics.checkExpressionValueIsNotNull(untarProcess, "untarProcess");
  this.waitAndCheckProcessOutput(untarProcess, "tar -xzf " + tarFile.toString() + " -C " + extractPath.toString(), 15L);

  logger.info("Running chmod on folder:" + extractPath.toString());
  List chmodCommand = Arrays.asList("chmod", "-R", "0777", extractPath.toString());
  Process chmodProcess = (new ProcessBuilder(new String[0]))
      .command(chmodCommand)
      .redirectErrorStream(true)
      .start();
  Intrinsics.checkExpressionValueIsNotNull(chmodProcess, "chmodProcess");
  this.waitAndCheckProcessOutput(chmodProcess, "chmod -R 0777 " + extractPath.toString(), 15L);
  logger.info("Extracted in " + this.printTime(System.currentTimeMillis() - start));
}

public final void waitAndCheckProcessOutput(@NotNull Process process, @NotNull String command, long timeoutInMinutes) {
  Intrinsics.checkParameterIsNotNull(process, "process");
  Intrinsics.checkParameterIsNotNull(command, "command");
  try {
      boolean finished = process.waitFor(timeoutInMinutes, TimeUnit.MINUTES);
      if (!finished) {
        logger.info("Timed out running command " + command);
      }
      String output = new BufferedReader(new InputStreamReader(process.getInputStream()))
        .lines()
        .collect(Collectors.joining());
      int exitCode = process.exitValue();
      if (exitCode != 0) {
        logger.info("Got exit code " + exitCode + " running command " + command + "\nGot output:\n" + output);
      }
  } catch (InterruptedException var8) {
      logger.info("Interrupted running command " + command + var8);
  }

}

@NotNull
public final String print(long timeInMillis) {
  Duration duration = Duration.ofMillis(timeInMillis);
  long minutes = duration.toMinutes();
  duration = duration.minusMinutes(minutes);
  long seconds = duration.getSeconds();
  duration = duration.minusSeconds(seconds);
  long millis = duration.toMillis();
  return minutes + "m " + seconds + "." + millis + "s";
}

Код Java:

public static synchronized Path untarFile(Path tarFile, Path extractPath) throws IOException {
    logger.info("Extracting file: " + tarFile.toString());
    logger.info("Destination path: " + extractPath.toString());
    long start = System.currentTimeMillis();
    List<String> untarCommand = Arrays.asList("tar", "-xzf", tarFile.toString(), "-C", extractPath.toString());
    Process untarProcess = new ProcessBuilder()
        .command(untarCommand)
        .redirectErrorStream(true)
        .start();
    ProcessUtil.waitAndCheckProcessOutput(untarProcess, "tar -xzf " + tarFile.toString() + " -C " + extractPath.toString());

    logger.info("Running chmod on folder:" + extractPath.toString());
    List<String> chmodCommand = Arrays.asList("chmod", "-R", "0777", extractPath.toString());
    Process chmodProcess = new ProcessBuilder()
        .command(chmodCommand)
        .redirectErrorStream(true)
        .start();
    ProcessUtil.waitAndCheckProcessOutput(chmodProcess, "chmod -R 0777 " + extractPath.toString());

    logger.info("Extracted in " + TimeUtil.print(System.currentTimeMillis() - start));
    return extractPath;
}

// In ProcessUtil.java
public static void waitAndCheckProcessOutput(Process process, String command, long timeoutInMinutes) {
    try {
        boolean finished = process.waitFor(timeoutInMinutes, TimeUnit.MINUTES);
        if (!finished) {
            logger.info("Timed out running command " + command);
        }
        String output = new BufferedReader(new InputStreamReader(process.getInputStream()))
            .lines()
            .collect(Collectors.joining());
        int exitCode = process.exitValue();
        if (exitCode != 0) {
            logger.info("Got exit code " + exitCode + " running command " + command
                                            + "\nGot output:\n" + output);
        }
    } catch (InterruptedException e) {
        logger.info("Interrupted running command " + command, e);
    }
}

// in TimeUtil.java
public static String print(long timeInMillis) {
    Duration duration = Duration.ofMillis(timeInMillis);
    long minutes = duration.toMinutes();
    duration = duration.minusMinutes(minutes);
    long seconds = duration.getSeconds();
    duration = duration.minusSeconds(seconds);
    long millis = duration.toMillis();
    return minutes + "m " + seconds + "." + millis + "s";
}

Itвызывается так:

IntStream.range(0, count).parallel().forEach{ unsynchronizedMethod() }

, где extractTar вызывается в этом unsynchronizedMethod.

Я на 95% уверен, что замедление не происходит в вызовах встроенной библиотеки, так как ни один изпроверяемые значения должны всегда быть нулевыми.

Интересно также отметить, что замедление не происходит для небольшого количества потоков, таких как 2. По мере увеличения числа потоков производительность ухудшается, что приводит меня кПодозреваю, что это на самом деле проблема параллелизма.

Я также попытался запустить код, декомпилированный в Java Kotlin, удаляя встроенный library вызывает, и этот код имеет ту же производительность, что и собственный код Java.

1 Ответ

0 голосов
/ 20 сентября 2018

Исправлено перемещением экстракта в

companion object {}

Не уверен, почему это решило проблему, но это

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...