简单循环太慢了

时间:2015-12-16 20:33:13

标签: algorithm scala complexity-theory

我不明白为什么下面的代码太慢了。这段代码的目标很简单:我有一组点,我想分成6个桶(每桶100000点)。代码:

Proving the simplification rules...

Proof failed.

1. ⋀x y xa evm1 A B used.
     x (?x27 x y xa evm1 A B used) ⟶ y (?x27 x y xa evm1 A B used) 
     ⟹ Says A B (packet A B Hi) ∈ used {x. y x}
     ⟶ Says A B (packet A B Hi) ∈ used {xa. x xa}

The error(s) above occurred for the goal statement:

mono  (λp x. x = [] ∨
         (∃evm1 A B used.
             x = Says A B (packet A B Hi) # evm1 ∧
             p evm1 ∧
             A ≠ B ∧ Says A B (packet A B Hi) ∉ used {x. p x}))`

访问map并附加到列表缓冲区需要一个恒定的时间,所以对我来说,这段代码的复杂性是O(n),其中n是要分割的点数。我可以提出一些建议,使这段代码更快吗?

5 个答案:

答案 0 :(得分:6)

以下重构不会产生与点数一样多的集合,并且依赖于Scala API,

object Main {
  def main(args : Array[String]) = {
    val labels = Array("1","2","3","4","5","6")
    val points = Array.fill(600000){0.0}

    val t1 = System.currentTimeMillis
    val xst = points.grouped(labels.size).toArray.transpose
    val m = (labels zip xst).toMap
    val t2 = System.currentTimeMillis

    println("fill values in = " +  (t2-t1) + " msecs")
  }
}

虽然原始代码需要几分钟,但这个需要大约700毫秒。

此代码可避免索引引用和更新现有集合。

使用我填充内存的代码更新(Alifirat)

object Main {
  def main(args : Array[String]) = {
    val labels = Array("1","2","3","4","5","6", "7")
    val points = Array.fill(7000000){0.0}

    val t1 = System.currentTimeMillis
    val xst = points.grouped(labels.size).toArray.transpose
    val m = (labels zip xst).toMap
    val t2 = System.currentTimeMillis

    println("fill values in = " +  (t2-t1) + " msecs")
  }
}

相同的代码,但7个桶的7,000 000点运行。

<强>更新

尝试

scala -J-Xmx4g

然后粘贴更新的代码。

<强>更新

如果最终地图映射到0.0的数组,以下证明在7000万点上非常快,

val m = labels.map(l => l -> Array.fill(10*1000*1000){0.0}).toMap

如果性能至关重要,那么已经建议我使用面向C的方法来证明内存和时间效率,可能会牺牲可伸缩性和组合性。

答案 1 :(得分:2)

皮肤猫的另一种方式:

// some example values to be encoded
$secret = 'xxxyyy';
$key = 'some key';
$params = array('a', 'b', 'b');

// concatenate the values into a single string ready for encoding
$imploded = implode(',', $params); // becomes string 'a,b,c'
$concatenated_str = 'a,b,c' . ',' . $secret; // becomes 'a,b,c,xxxyyy'

// encode the string
$codice_sha1 = sha1($concatenated_str); // creates a SHA1 hash of the string
$codice = base64_encode("{$key}:{$codice_sha1}"); // creates base64 encoding of the SHA1 hash

我没有正确地对它进行基准测试,但它是我尝试过的最快的事情(不再是 - 请参阅我的其他答案)

答案 2 :(得分:1)

正如@Noah在评论中正确注意到的那样,您不必将缓冲区推回地图。这应该足够了:

val values = m.getOrElseUpdate(currentLabel, ListBuffer())
values += point

或者您可以使用功能方法来实现,如果您使用scala,则建议使用此方法:

val labels = Array("1","2","3","4","5","6")
val points = Array.fill(60000){2.0}
val t1 = System.currentTimeMillis
val m = points.zipWithIndex.groupBy {
  case (point, i) => labels(i % labels.size)
}.mapValues(arr => arr.map(_._1).toList)

val t2 = System.currentTimeMillis
println(m)
println("fill values in = " +  (t2-t1) + " msecs")

请注意 - 这里没有可变数据结构

答案 3 :(得分:1)

这里(作为一个单独的答案,因为它与我的第一个答案完全不同)是另一个“C风格”,它可以处理可变数量的桶。

有趣的是,在我的机器上,它的速度是Espen Brekke的另一个C风格答案的两倍。更新:我把它拿回来 - 两个都运行得如此之快,实际执行时间被启动时间掩盖等。运行100倍的点数(60000000),Espen的速度大约是其两倍(但具有固定数量的存储桶) - 大约220ms,而大约420ms

UPDATE2:Espen的最新版本现在或多或少与下面相同(除了使用异常而不是显式边界检查),毫不奇怪,它以相似的速度运行。

另请注意,这两个版本都不会创建存储区阵列。在我的机器上,这大约是另外200毫秒(对于任一版本),所以大约50%-100%的时间将东西放入桶中......

因此,此变体比OP的原始代码(在我的机器上)快约100,000倍!

TimerEvent.TIMER_COMPLETE

答案 4 :(得分:0)

有时,正确的方法是改变功能风格。 我用C风格的Scala编写了一个解决方案。

def main(args : Array[String]) = {
val labels = Array("1","2","3","4","5","6")
val points = Array.fill(600000){0.0}


val numChannels=6;
val channels=new Array[Array[Double]](numChannels);
for(i<-0 until numChannels){
  channels(i)=new Array[Double]((points.length/6));
}

var from=0;

val t1 = System.currentTimeMillis

for(i<-0 until numChannels){
  val channel=channels(i);
  from=i
  var to=0;
  try{
    while(true){
      channel(to)=points(from);
      to=to+1;
      from=from+numChannels;
    }
  } catch {
    case e:ArrayIndexOutOfBoundsException =>{
      //Ok finished this channel
    }
  }
}

val t2 = System.currentTimeMillis

println("fill values in = " +  (t2-t1) + " msecs")
println("from:"+from)
}

此解决方案仅执行需要完成的工作,而不再执行。

在我的mashine上使用8毫秒 榆树的代码使用309毫秒 原版花了221226毫秒。

虽然我喜欢Scala,但重要的是要记住它擅长隐藏其操作的计算成本。集合上的大多数操作至少为每个元素引入了几个过程调用。