Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to speed up this function?

I'm comparing performance of this F# function:

let e28 N =                               
  seq {for i in 2L..2L..N do for j in 1..4 -> i} |> Seq.scan (+) 1L |> Seq.sum  

with Python 3.3 equivalents:

def e28a(N = 100000):
    diagNumber = 1                             
    sum        = diagNumber                
    for width in range(2, N+1, 2):
        for j in range(4):          
            diagNumber += width             
            sum        += diagNumber            
    return sum

import itertools as it
def e28b(N = 100000):
    return sum(it.accumulate(it.chain([1], (i for i in range(2, N+1, 2) for j in range(4)))))    

import numpy as np
def e28c(N = 100000):
    return np.sum(np.cumsum(np.fromiter(chain([1], (i for i in range(2, N+1, 2) for j in range(4))), np.int64)))

and I'm getting 64-bit CPython 3.3.1 performance on Windows 7 about 574 times slower than C++. Here are the times for N = 100000:

e28: 23ms; e28a: 48.4ms; e28b: 49.7ms; e28c: 40.2ms; C++ version: 0.07ms

Is there a low hanging fruit in optimizing Python code without altering the underlying algorithm?

like image 677
Paul Jurczak Avatar asked Feb 16 '23 00:02

Paul Jurczak


1 Answers

The F# version can be sped up by ~10x by switching to a procedural, mutable approach (like your python e28a). When the "payload operation" (in this case, just +) is so trivial, the use of combinators ends up adding a relatively significant overhead. As a side note, Seq.sum uses checked arithmetic, which also adds a touch of overhead.

One of the nice things about F# is that you can fall back to procedural/mutable style if needed for a perf-critical hot path.

let e28_original N =
  seq {
    for i in 2UL..2UL..N do 
        for j in 1..4 do
            yield i
  }
  |> Seq.scan (+) 1UL
  |> Seq.sum

let e28_mutable N = 
  let mutable sum = 1UL
  let mutable total = sum                            
  for i in 2UL..2UL..N do 
      for j in 1..4 do
         sum <- sum + i
         total <- total + sum
  total

let time f =
    f () |> ignore // allow for warmup / JIT
    let sw = System.Diagnostics.Stopwatch.StartNew()
    let result = f ()
    sw.Stop()
    printfn "Result: %A Elapsed: %A" result sw.Elapsed

time (fun _ -> e28_original 100000UL)
time (fun _ -> e28_mutable 100000UL)

Result

Result: 666691667100001UL Elapsed: 00:00:00.0429414
Result: 666691667100001UL Elapsed: 00:00:00.0034971
like image 101
latkin Avatar answered Feb 18 '23 15:02

latkin