简体   繁体   English

Kotlin 中的快速 I/O

[英]Fast I/O in Kotlin

Kotlin for competitive programming suggests the following code for reading console input.用于竞争性编程的 Kotlin建议使用以下代码来读取控制台输入。

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

Until now for every competitive problem I've used the same approach and to be honest, it has never disappointed.到目前为止,对于每一个竞争问题,我都使用相同的方法,老实说,它从未让人失望。

But for certain problems where the count of input integers is very large (close to 2 * 10^6) this method is just too slow and results in TLE (Time Limit Exceeded) .但是对于输入整数的计数非常大(接近 2 * 10^6)的某些问题,此方法太慢并导致 TLE (Time Limit Exceeded)

Is there even a faster way to read input from console?是否有更快的方法从控制台读取输入?

If you suspect that the .split() call is the bottleneck, you could explore some of the alternatives in this thread .如果您怀疑.split()调用是瓶颈,您可以在这个线程中探索一些替代方案。

If you suspect that the toInt() call is the bottleneck, perhaps you could try parallelizing the streams using the Java 8 stream API.如果您怀疑toInt()调用是瓶颈,也许您可以尝试使用 Java 8 stream API 并行化流。 For example:例如:

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

For best performance, you could probably combine the two methods.为了获得最佳性能,您可以将这两种方法结合起来。

I believe, toInt() convertions are more expensive, than split(" ") .我相信, toInt()转换比split(" ")更昂贵。

Are you sure you need to convert to Int all strings of the input in the very beginning?您确定一开始就需要将输入的所有字符串都转换为Int吗?

It depends on a task, but sometimes part of this convertions could be avoided.这取决于一项任务,但有时可以避免部分转换。 For instance, if you have a task " check if there is no negative numbers in the input ", you may convert strings to Int one by one, and if you met a negative one, no need to convert others.例如,如果你有一个任务“检查输入中是否没有负数”,你可以将字符串一一转换为Int ,如果遇到负数,则无需转换其他字符串。

I think that JMH could be useful here.我认为JMH在这里可能很有用。 You can run the benchmark similar to the one bellow and try to identify your bottlenecks.您可以运行类似于下面的基准测试并尝试确定您的瓶颈。

Note that this is in Mode.SingleShotTime , and so emulates the scenarion where JIT has little opprotunity to do it's thing.请注意,这是在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() }
}

The stats on my machine are:我机器上的统计数据是:

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

So splitting the string and mapping to list of Int s takes ~ 187 ms.因此,拆分字符串并映射到Int列表需要大约 187 毫秒。 Allowing JIT to warm up ( Mode.AverageTime ) gives me:允许 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

Whether this is fast or slow depends on the curmstances but are you sure that the input transformation here is the reason you get TLE?这是快还是慢取决于具体情况,但您确定这里的输入转换是您获得 TLE 的原因吗?

Edit : If you do think that split(" ").map{ str -> str.toInt() } is too slow, you could replace creating the two lists (one from split and one from map ) with a single list by splitting and transforming in one go.编辑:如果您确实认为split(" ").map{ str -> str.toInt() }太慢,您可以通过拆分用单个列表替换创建两个列表(一个来自split ,一个来自map )并转换成一个 go。 I wrote a quick hack off kotlin.text.Regex.split that does that and it is about 20% faster.我写了一个快速破解kotlin.text.Regex.split的方法,它的速度提高了大约 20%。

If in your use case you need to examine only part of the input, splitToSequence is probably a better option.如果在您的用例中您只需要检查输入的一部分, splitToSequence可能是一个更好的选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM