【问题标题】:Fast I/O in KotlinKotlin 中的快速 I/O
【发布时间】:2021-08-17 04:50:42
【问题描述】:

Kotlin for competitive programming 建议使用以下代码来读取控制台输入。

readLine()!!.split(" ").map{ str -> str.toInt() } //read space separated Integer from console

到目前为止,对于每一个竞争问题,我都使用相同的方法,老实说,它从未让我失望。

但对于某些输入整数计数非常大的问题(接近 2 * 10^6),此方法太慢并导致 TLE (超出时间限制) .

还有更快的方法从控制台读取输入吗?

【问题讨论】:

  • 你知道你的时间预算是多少吗?此外,在执行您的解决方案时,是否允许 JIT 预热?

标签: performance kotlin optimization io


【解决方案1】:

如果您怀疑 .split() 调用是瓶颈,您可以探索一些替代方法 in this thread

如果您怀疑 toInt() 调用是瓶颈,也许您可​​以尝试使用 Java 8 流 API 并行化流。例如:

readLine()!!.split(" ").parallelStream().map { str -> str.toInt() }...

为了获得最佳性能,您可以将这两种方法结合起来。

【讨论】:

    【解决方案2】:

    我相信,toInt() 的转换比split(" ") 更昂贵。

    您确定需要在一开始就转换为输入的Int所有字符串吗?

    这取决于任务,但有时可以避免部分转换。 例如,如果你有一个任务“检查输入中是否没有负数”,你可以将字符串一一转换为Int,如果遇到负数,则不需要转换他人。

    【讨论】:

      【解决方案3】:

      我认为JMH 在这里可能很有用。您可以运行类似于下面的基准测试并尝试找出您的瓶颈。

      请注意,这是在 Mode.SingleShotTime 中,因此模拟了 JIT 几乎没有机会做这件事的场景。

      import org.openjdk.jmh.annotations.*
      import java.util.concurrent.TimeUnit
      import kotlin.random.Random
      
      //@BenchmarkMode(Mode.AverageTime)
      @BenchmarkMode(Mode.SingleShotTime)
      @OutputTimeUnit(TimeUnit.MILLISECONDS)
      @State(Scope.Benchmark)
      open class SplitToInt {
      
          val count = 2 * 1_000_000
      
          lateinit var stringToParse: String
          lateinit var tokensToParse: List<String>
      
          @Setup(Level.Invocation)
          fun setup() {
              stringToParse = (0..count).map { Random.nextInt(0, 100) }.joinToString(separator = " ")
              tokensToParse = (0..count).map { Random.nextInt(0, 100) }.map { it.toString() }
          }
      
          @Benchmark
          open fun split() =
              stringToParse.split(" ")
      
          @Benchmark
          open fun map_toInt() =
              tokensToParse.map { it.toInt() }
      
      
          @Benchmark
          open fun split_map_toInt() =
              stringToParse.split(" ").map { it.toInt() }
      }
      
      

      我机器上的统计数据是:

      Benchmark                   Mode  Cnt    Score   Error  Units
      SplitToInt.map_toInt          ss        48.666          ms/op
      SplitToInt.split              ss       124.899          ms/op
      SplitToInt.split_map_toInt    ss       186.981          ms/op
      
      

      因此,拆分字符串并映射到 Ints 列表大约需要 187 毫秒。允许 JIT 预热 (Mode.AverageTime) 给我:

      Benchmark                   Mode  Cnt    Score    Error  Units
      SplitToInt.map_toInt        avgt    5   30.670 ±  6.979  ms/op
      SplitToInt.split            avgt    5  108.989 ± 23.569  ms/op
      SplitToInt.split_map_toInt  avgt    5  120.628 ± 27.052  ms/op
      
      

      这是快还是慢取决于具体情况,但您确定这里的输入转换是您获得 TLE 的原因吗?

      编辑:如果您确实认为split(" ").map{ str -&gt; str.toInt() } 太慢,您可以通过拆分将创建两个列表(一个来自split,一个来自map)替换为一个列表并一口气转型。我写了一个快速破解kotlin.text.Regex.split,它可以做到这一点,而且速度提高了大约 20%。

      如果在您的用例中您只需要检查输入的一部分,splitToSequence 可能是更好的选择。

      【讨论】:

        猜你喜欢
        • 2016-08-12
        • 2017-09-20
        • 2020-11-17
        • 1970-01-01
        • 1970-01-01
        • 2010-10-16
        • 2021-06-05
        • 2018-10-25
        • 2013-09-22
        相关资源
        最近更新 更多