【问题标题】:What is a fast way to convert a string of two characters to an array of booleans?将两个字符的字符串转换为布尔数组的快速方法是什么?
【发布时间】:2016-07-04 14:28:21
【问题描述】:

我有一个长字符串(有时超过 1000 个字符),我想将其转换为布尔值数组。而且它需要非常快速地多次执行此操作。

let input: String = "001"
let output: [Bool] = [false, false, true]

我的幼稚尝试是这样的:

input.characters.map { $0 == "1" }

但这比我想要的要慢得多。我的分析告诉我map 是减速的地方,但我不确定我能做到多简单。

我觉得如果没有 Swift/ObjC 的开销,这将是非常糟糕的。在 C 语言中,我认为这是一个简单的 for 循环,其中一个字节的内存与一个常量进行比较,但我不确定我应该查看哪些函数或语法。

有没有办法更快地做到这一点?

更新:

我也试过了

output = []
for char in input.characters {
    output.append(char == "1")
}

而且速度提高了大约 15%。我希望的远不止这些。

【问题讨论】:

  • 用原始的 for..in 检查
  • @dimpiax 究竟如何?我尝试手动 for 循环编辑了这个问题,它确实有点帮助。
  • “001”的样本大小对于实际可测量的差异来说有点小。你能提供更大的样本集吗?您也无法测量循环超过 3 个字符所需的时间有任何差异。 (附加调试器?无效结果!)
  • 另外,如果你对 NSString 进行(无桥接)强制转换,你可以使用 .UTF8String() 来获取 const char * 的数组,如果你假设它实际上与布尔数组相同它总是 0 或 1
  • 我的实际数据是这些 5 到 1200 个字符长的数据。

标签: ios swift


【解决方案1】:

多走一步应该会加快速度。使用reserveCapacity 将在循环开始之前调整数组的大小,而不是在循环运行时尝试这样做。

var output = [Bool]()
output.reserveCapacity(input.characters.count)
for char in input.characters {
    output.append(char == "1")
}

【讨论】:

  • 奇怪的是,这会产生相反的效果。添加 `reserve capacity 线使其慢了大约 25%。 :(
  • 这很有趣。我想知道启用 -Ofast 是否会导致同样的减速。很高兴知道在reserveCapacity 实际导致速度增加之前数组需要多大。
【解决方案2】:

使用withCString(_:) 检索原始UnsafePointer<Int8>。对其进行迭代并与 49 进行比较("1" 的 ascii 值)。

【讨论】:

    【解决方案3】:

    这样更快:

    // Algorithm 'A'
    let input = "0101010110010101010"
    var output = Array<Bool>(count: input.characters.count, repeatedValue: false)
    for (index, char) in input.characters.enumerate() where char == "1" {
        output[index] = true
    }
    

    更新:input = "010101011010101001000100000011010101010101010101"

    0.0741 / 0.0087,这种方法比作者的方法快 8.46 倍。数据相关性越大,越积极。

    此外,使用nulTerminatedUTF8 的速度略有提高,但并不总是高于算法A

    // Algorithm 'B'
    let input = "10101010101011111110101000010100101001010101"
    var output = Array<Bool>(count: input.nulTerminatedUTF8.count, repeatedValue: false)
    for (index, code) in input.nulTerminatedUTF8.enumerate() where code == 49 {
        output[index] = true
    }
    

    在结果图中出现,输入长度2196,其中第一个和最后一个0..1,A - 第二个,B - 第三个点。 A:0.311 秒,B:0.304 秒

    【讨论】:

    • 为什么这样更快?是不是因为我们创建了整个最终数组,然后修改它,而不是在迭代原始数组时扩大它?如果是这样,在我看来这是map 的底层实现中的一个缺陷,应该报告为一个错误。
    • @matt 不,当map 这样做时,您不是为每个索引编写值的主要思想。
    • 测试编译时使用了哪些优化设置?
    • @Catfish_Man 在操场上测试
    • 啊,我强烈建议在操场外重新测试。它们对性能有非常大的影响,并且可以使测量结果偏离很多。
    【解决方案4】:
    import Foundation
    
    let input:String = "010101011001010101001010101100101010100101010110010101010101011001010101001010101100101010100101010101011001010101001010101100101010100101010"
    var start  = clock()
    var output = Array<Bool>(count: input.nulTerminatedUTF8.count, repeatedValue: false)
    var index = 0
    for val in input.nulTerminatedUTF8 {
        if val != 49 {
            output[index] = true
        }
        index+=1
    }
    var diff = clock() - start;
    var msec = diff * 1000 / UInt(CLOCKS_PER_SEC);
    print("Time taken \(Double(msec)/1000.0) seconds \(msec%1000) milliseconds");
    

    这应该非常快。试试看。 010101011010101001000100000011010101010101010101 需要 0.039 秒。

    【讨论】:

    • 我发现,如果您先更改repeatedValue:true,然后更改if val == 49 { output[index] = false },那么在大型数据集上的性能会明显提高。可能 == 比 != 快。您可以使用您的数据集进行尝试,看看是否可以获得更好的性能。
    • @AlexWayne,把你的数据放在要点上。我将测试性能。测试 Pradeep 与我的解决方案 - 结果:2.546 与 0.826。
    • 我同意。 @dimpiax 的速度提高了 3 倍。
    • @dimpiax 我对游乐场进行了一些基准测试,pradeeps 解决方案在 1024 个字符上运行时似乎快 40 倍。我的测试游乐场:gist.github.com/Squeegy/328a4ca72e0db22793ed
    【解决方案5】:

    这应该比 enumerate() where char == "1" 版本快一点(500_000 个交替 1 和 0 的时间为 0.557 秒,而 diampiax 的算法“A”为 1.159 秒)

    let input = inputStr.utf8
    let n = input.count
    var output = [Bool](count: n, repeatedValue: false)
    let one = UInt8(49) // 1
    for (idx, char) in input.enumerate() {
        if char == one { output[idx] = true }
    }
    

    但它的可读性也差很多;-p

    编辑:两个版本都比地图变体慢,也许你忘了优化编译?

    【讨论】:

      【解决方案6】:

      更实用的风格呢?今天肯定不是最快的(47 毫秒)...

      import Cocoa
      
      let start = clock()
      
      let bools = [Bool](([Character] ("010101011001010101001010101100101010100101010110010101010101011001010101001010101100101010100101010101011001010101001010101100101010100101010".characters)).map({$0 == "1"}))
      
      let msec = (clock() - start) * 1000 / UInt(CLOCKS_PER_SEC);
      print("Time taken \(Double(msec)/1000.0) seconds \(msec%1000) milliseconds");
      

      【讨论】:

        【解决方案7】:

        我需要进行一些测试才能确定,但​​我认为包括原始地图在内的许多方法的一个问题是,他们需要遍历字符串以计算字符数,然后再进行第二次实际处理字符。

        你试过了吗:

        let output = [Bool](input.characters.lazy.map { $0 == "1" })
        

        这可能只进行一次迭代。

        另一件可以加快速度的事情是,如果您可以避免使用字符串,而是使用适当编码的字符数组(特别是如果是更固定大小的单位(例如 UTF16 或 ASCII)。那么长度查找将是 O (1) 而不是 O(n) 并且迭代也可能更快

        顺便说一句,总是在启用优化器的情况下测试性能,而不是在 Playground 中测试,因为性能特征完全不同,有时相差 100 倍。

        【讨论】:

          【解决方案8】:

          我猜这是尽可能快的:

          let targ = Character("1")
          let input: String = "001" // your real string goes here
          let inputchars = Array(input.characters)
          var output:[Bool] = Array.init(count: inputchars.count, repeatedValue: false)
          inputchars.withUnsafeBufferPointer {
              inputbuf in
              output.withUnsafeMutableBufferPointer {
                  outputbuf in
                  var ptr1 = inputbuf.baseAddress
                  var ptr2 = outputbuf.baseAddress
                  for _ in 0..<inputbuf.count {
                      ptr2.memory = ptr1.memory == targ
                      ptr1 = ptr1.successor()
                      ptr2 = ptr2.successor()
                  }
              }
          }
          // output now contains the result
          

          原因是,由于使用了缓冲区指针,我们只是在连续的内存中循环,就像通过递增指针来循环遍历 C 数组的方式一样。因此,一旦我们通过了初始设置,这应该和在 C 中一样快。

          EDIT在实际测试中,OP原来的方法和这个方法的时间差就是

          13.3660290241241
          

          0.219357967376709
          

          这是一个非常显着的加速。不过,我赶紧补充一下,我已经排除了计时测试的初始设置。这一行:

          let inputchars = Array(input.characters)
          

          ...特别贵。

          【讨论】:

            猜你喜欢
            • 2015-02-23
            • 2011-06-26
            • 1970-01-01
            • 1970-01-01
            • 2015-04-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多