# Very slow execution time in comparison even to Python

**URL:** https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223
**Category:** Performance
**Created:** [October 29, 2020, 6:25am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223 "2020-10-29T06:25:39Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![Yury\_Kotlyarov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yury_kotlyarov/32/19020_2.png) [@Yury\_Kotlyarov](https://discourse.julialang.org/u/Yury_Kotlyarov)
#### Post date: [October 29, 2020, 6:25am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/1 "2020-10-29T06:25:39Z")

</div>

I am new to Julia.

While choosing a programming language for our next project wrote a short program in Julia:

```julia
using LinearAlgebra
using Statistics
using Random

import Base: +, -

struct Point
    x::Float64
    y::Float64
    z::Float64
end

+(a::Point, b::Point) = Point(a.x + b.x, a.y + b.y, a.z + b.z)
-(a::Point, b::Point) = Point(a.x - b.x, a.y - b.y, a.z - b.z)

struct Plane
    point::Point
    normal::Array{Float64}
end

ArrayToMatrix(array) = vcat(array...)

function mean_center(points)
    centroid = Point(mean(ArrayToMatrix([[p.x p.y p.z] for p in points]), dims = 1)...)
    points_centered = [p - centroid for p in points]
    centroid, points_centered
end

function best_fit_plane(points)
    centroid, centered_points = mean_center(points)
    m = ArrayToMatrix([[p.x p.y p.z] for p in centered_points])
    u, _, _ = svd(transpose(m))
    Plane(centroid, u[:,end])
end

function main()
  MIN = -2000
  MAX = 2000
  BATCHES_COUNT = 1000000
  POINTS_PER_BATCH = 8

  generatePoint() = map(x -> x + MIN, rand!(zeros(3)) * (MAX - MIN))
  generateBatch() = map(x -> generatePoint(), zeros(POINTS_PER_BATCH))
  generateBatches() = map(x -> generateBatch(), zeros(BATCHES_COUNT))

  for b in generateBatches()
    points = [Point(coord...) for coord in b]
    println(best_fit_plane(points))
  end
end

main()

```

running this code with following command:

```julia
time julia -O 3 -t auto -p auto bench.jl

```

and get on my mac book:

```julia
julia -p auto -t auto -O 3 bench.jl 210,16s user 112,23s system 30% cpu 17:43,32 total

```

Almost 18 mins. The same code in Python + skspatial finishes in 11 mins average. Haskell ~ 1 min.

What I made the wrong way?

---

<div class="post-metadata">

### Author: ![lungben](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lungben/32/12314_2.png) [@lungben](https://discourse.julialang.org/u/lungben)
#### Post date: [October 29, 2020, 6:43am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/2 "2020-10-29T06:43:31Z")

</div>

Welcome!

> [@Yury\_Kotlyarov](#):
>
> ```julia
> normal::Array{Float64}
> 
> ```

The array is an abstract type here because the dimension is not given.  
The following should be much more performant:

```julia
normal::Array{Float64, 1}
normal::Vector{Float64} # equivalent to the one above

```

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [October 29, 2020, 6:58am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/3 "2020-10-29T06:58:55Z")

</div>

There are many things to improve here. In addition to the abstract type in `Plane`, I believe the _main_ issue is the repeated (and very slow) conversions of `Point`s into matrix. You must find a way to not do that.

I’ll have a look later, unless someone beats me to it.

---

<div class="post-metadata">

### Author: ![jbrea](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jbrea/32/3879_2.png) [@jbrea](https://discourse.julialang.org/u/jbrea)
#### Post date: [October 29, 2020, 8:13am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/4 "2020-10-29T08:13:29Z")

</div>

One quick thing to improve performance (and readability) is to define also

```julia
Base.:/(a::Point, s::Number) = Point(a.x/s, a.y/s, a.z/s)

```

and then write

```julia
function mean_center(points)
    centroid = mean(points)
    points_centered = [p - centroid for p in points]
    centroid, points_centered
end

```

---

<div class="post-metadata">

### Author: ![jbrea](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jbrea/32/3879_2.png) [@jbrea](https://discourse.julialang.org/u/jbrea)
#### Post date: [October 29, 2020, 8:16am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/5 "2020-10-29T08:16:40Z")

</div>

> [@Yury\_Kotlyarov](#):
>
> `m = ArrayToMatrix([[p.x p.y p.z] for p in centered_points])`

A little bit faster would be

```julia
m = [getproperty(p, c) for p in centered_points, c in (:x, :y, :z)]

```

---

<div class="post-metadata">

### Author: ![Albert\_de\_montserrat](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albert_de_montserrat/32/9135_2.png) [@Albert\_de\_montserrat](https://discourse.julialang.org/u/Albert_de_montserrat)
#### Post date: [October 29, 2020, 8:17am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/6 "2020-10-29T08:17:52Z")

</div>

A bit less elegant perhaps but this already gives you a x2 speed-up

```julia
function mean_center2(points,centroid)
    for p in points
        centroid += [p.x; p.y; p.z]
    end
    centroid = Point(centroid./length(points)...)
    points_centered = [p - centroid for p in points]
    centroid, points_centered
end

function best_fit_plane2(points,centroid,m)
    centroid, centered_points = mean_center2(points,centroid)
    @inbounds for p ∈ 1:8
        m[p,1],m[p,2],m[p,3] = centered_points[p].x,centered_points[p].y, centered_points[p].z
    end    
    u, _, _ = svd(transpose(m))
    Plane(centroid, u[:,end])
end

function main()
  MIN = -2000
  MAX = 2000
  BATCHES_COUNT = 10000
  POINTS_PER_BATCH = 8

  generatePoint() = map(x -> x + MIN, rand(3) * (MAX - MIN))
  generateBatch() = map(x -> generatePoint(), zeros(POINTS_PER_BATCH))
  generateBatches() = map(x -> generateBatch(), zeros(BATCHES_COUNT))

  for b in generateBatches()
    points = [Point(coord...) for coord in b]
    # println(best_fit_plane(points))
    best_fit_plane(points)
  end
end

function main2()
    MIN = -2000
    MAX = 2000
    BATCHES_COUNT = 10000
    POINTS_PER_BATCH = 8
    m = fill(0.0,POINTS_PER_BATCH,3)
    centroid = fill(0.0,3)

    generatePoint() = Point( rand(3) * (MAX - MIN) .+ MIN...)
    generateBatch() = map(x -> generatePoint(), zeros(POINTS_PER_BATCH))
    generateBatches() = map(x -> generateBatch(), zeros(BATCHES_COUNT))
  
    for b in generateBatches()
      best_fit_plane2(b,centroid,m)
    end
end

@benchmark main()
BenchmarkTools.Trial: 
  memory estimate: 107.57 MiB
  allocs estimate: 1149496
  --------------
  minimum time: 196.844 ms (9.15% GC)
  median time: 216.148 ms (12.00% GC)
  mean time: 269.495 ms (23.74% GC)
  maximum time: 955.288 ms (44.72% GC)
  --------------
  samples: 19
  evals/sample: 1

@benchmark main2()
BenchmarkTools.Trial: 
  memory estimate: 89.10 MiB
  allocs estimate: 949496
  --------------
  minimum time: 136.543 ms (3.96% GC)
  median time: 138.251 ms (4.29% GC)
  mean time: 139.093 ms (4.30% GC)
  maximum time: 154.269 ms (3.36% GC)
  --------------
  samples: 36
  evals/sample: 1

```

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [October 29, 2020, 8:20am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/7 "2020-10-29T08:20:54Z")

</div>

> [@Yury\_Kotlyarov](#):
>
> `println(best_fit_plane(points))`

Do you really want this thing to print all the time? It’s going to spend its time printing mostly.

---

<div class="post-metadata">

### Author: ![jbrea](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jbrea/32/3879_2.png) [@jbrea](https://discourse.julialang.org/u/jbrea)
#### Post date: [October 29, 2020, 8:24am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/8 "2020-10-29T08:24:06Z")

</div>

> [@Yury\_Kotlyarov](#):
>
> ```julia
> generatePoint() = map(x -> x + MIN, rand!(zeros(3)) * (MAX - MIN))
> generateBatch() = map(x -> generatePoint(), zeros(POINTS_PER_BATCH))
> generateBatches() = map(x -> generateBatch(), zeros(BATCHES_COUNT))
> 
> ```

Here there is not so much performance gain to be expected, but for readability I would prefer

```julia
generatePoint(min, max) = Point(rand() * (max - min) + min, rand() * (max - min) + min, rand() * (max - min) + min) # or a bit less performant but shorter Point((rand(3)*(max - min) .+ min)...)
generateBatch(points_per_batch) = [generatePoint() for _ in 1:points_per_batch]

```

---

<div class="post-metadata">

### Author: ![paulmelis](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/paulmelis/32/35063_2.png) [@paulmelis](https://discourse.julialang.org/u/paulmelis)
#### Post date: [October 29, 2020, 8:28am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/9 "2020-10-29T08:28:44Z")

</div>

> [@DNF](#):
>
> Do you really want this thing to print all the time? It’s going to spend its time printing mostly.

It seems printing to a terminal in Julia is pretty slow compared to Python, so that will be noticeable for lots of output to a terminal:

```julia
paulm@cmstorm 09:25:/data/examples/julia$ cat print_integers.py 
for i in range(1, 1000001):
    print(i)

paulm@cmstorm 09:25:/data/examples/julia$ cat print_integers.jl 
for i in 1 : 1000000
    println(i)
end

paulm@cmstorm 09:25:/data/examples/julia$ time python print_integers.py
...
999998
999999
1000000

real	0m2.440s
user	0m1.196s
sys	0m1.093s

paulm@cmstorm 09:25:/data/examples/julia$ time julia -O3 print_integers.jl
...
real	0m5.790s
user	0m2.909s
sys	0m2.863s

paulm@cmstorm 09:26:/data/examples/julia$ time python print_integers.py > /dev/null

real	0m0.277s
user	0m0.274s
sys	0m0.003s

paulm@cmstorm 09:27:/data/examples/julia$ time julia -O3 print_integers.jl > /dev/null

real	0m0.375s
user	0m0.289s
sys	0m0.071s

```

I think there was a github issue for this as well. Ah, it’s [slow printing in terminals · Issue #36639 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/36639), but that doesn’t provide much information yet, other than a post on this forum.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [October 29, 2020, 8:36am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/10 "2020-10-29T08:36:25Z")

</div>

I would not use my own definition of `Point`. Instead I would just use StaticArrays. You can roll your own, like you have, but then you should add some more functionality.

You should absolutely avoid the super-expensive conversion from points to matrix, and I would not generate the whole set of batches at once, just generate one at a time.

Here’s an example implementation using `SVector`:

```julia

using StaticArrays
using Statistics: mean
using LinearAlgebra: svd

struct Plane
    point::SVector{3, Float64}
    normal::SVector{3, Float64}
end

points2matrix(points) = reshape(reinterpret(Float64, points), 3, :)

function mean_center(points)
    centroid = mean(points)
    points_centered = points .- (centroid,)
    return centroid, points_centered
end

function best_fit_plane(points)
    centroid, centered_points = mean_center(points)
    M = points2matrix(centered_points)
    u, = svd(M)
    return Plane(centroid, u[:,end])
end

function main()
    MIN = -2000
    MAX = 2000
    BATCHES_COUNT = 1000000
    POINTS_PER_BATCH = 8
  
    generatePoint() = rand(SVector{3, Float64}) .* (MAX - MIN) .+ MIN
    
    generateBatch() = [generatePoint() for _ in 1:POINTS_PER_BATCH]
    generateBatches() = (generateBatch() for _ in 1:BATCHES_COUNT)
    
    planes = [best_fit_plane(batch) for batch in generateBatches()]
    return planes
end

```

While benchmarking I suggest reducing the `BATCHES_COUNT` which is needlessly large for benchmarking.

Notice the definition of `generateBatches`. It has `()` instead of `[]`. This is a generator, so each batch is generated on demand.

Runtime for the above code is ~6.5 seconds. It doesn’t print though, so you will need to add that if you like.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [October 29, 2020, 8:46am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/11 "2020-10-29T08:46:00Z")

</div>

Here’s a multithreaded version that scales pretty well (3.5x faster with 4 threads in my laptop):

```julia
function main_threaded()
    MIN = -2000
    MAX = 2000
    BATCHES_COUNT = 1000000
    POINTS_PER_BATCH = 8
  
    generatePoint() = rand(SVector{3, Float64}) .* (MAX - MIN) .+ MIN
    generateBatch() = [generatePoint() for _ in 1:POINTS_PER_BATCH]
    
    planes = Vector{Plane}(undef, BATCHES_COUNT)
    Threads.@threads for i in 1:BATCHES_COUNT
        planes[i] = best_fit_plane(generateBatch())
    end
    return planes
end

```

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [October 29, 2020, 9:05am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/12 "2020-10-29T09:05:12Z")

</div>

> [@paulmelis](#):
>
> It seems printing to a terminal in Julia is pretty slow compared to Python, so that will be noticeable for lots of output to a terminal:

I have a hard time believing that it’s useful to print 1 million calculated `Plane`s objects to screen. Printing to file, ok, but I don’t think this is particularly useful, and it messes up benchmarking.

---

<div class="post-metadata">

### Author: ![moeddel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/moeddel/32/18641_2.png) [@moeddel](https://discourse.julialang.org/u/moeddel)
#### Post date: [October 29, 2020, 9:08am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/13 "2020-10-29T09:08:59Z")

</div>

At least one should avoid printing inside the hot loop.

---

<div class="post-metadata">

### Author: ![paulmelis](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/paulmelis/32/35063_2.png) [@paulmelis](https://discourse.julialang.org/u/paulmelis)
#### Post date: [October 29, 2020, 9:10am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/14 "2020-10-29T09:10:34Z")

</div>

See my post in [Why is printing to a terminal slow? - #22 by paulmelis](https://discourse.julialang.org/t/why-is-printing-to-a-terminal-slow/42987/22) just yet, e.g. piping through `less` is also very slow

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [October 29, 2020, 9:17am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/15 "2020-10-29T09:17:32Z")

</div>

If the objective is to just speed up the posted script, then I think the conversation is going just fine. But if an objective is to compare versions of the script in different languages, then I think the Python and Haskell code should also be posted. I myself won’t be able to make language comparisons, but I’m sure someone else can.  
Besides, they may spot performance differences that are happening because code that looks equivalent actually isn’t. A (probably unrelated) example is how array-slicing bracket-syntax makes copies in Julia but views in Python.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [October 29, 2020, 9:23am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/16 "2020-10-29T09:23:14Z")

</div>

A note: In my posted example, roughly 80%-90% of the time is now spent inside `svd`. If you want more speed-ups, you should consider whether you can do better than the standard `svd`.

---

<div class="post-metadata">

### Author: ![moeddel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/moeddel/32/18641_2.png) [@moeddel](https://discourse.julialang.org/u/moeddel)
#### Post date: [October 29, 2020, 9:33am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/17 "2020-10-29T09:33:45Z")

</div>

```julia
using LinearAlgebra, Statistics, Random, StaticArrays

struct Plane
    point::SVector{3,Float64}
    normal::SVector{3,Float64}
end

function mean_center(points::SMatrix)
    centroid = mean(points,dims=2)
    points_centered = points .- centroid
    centroid, points_centered
end

function best_fit_plane(points::SMatrix)
    centroid, centered_points = mean_center(points)
    u, = svd(centered_points) # you can leave _, _ if you are interested in the first output only
    Plane(centroid, u[:,end])
end

function main()
	MIN = -2000
	MAX = 2000
	BATCHES_COUNT = 10000
	POINTS_PER_BATCH = 8
	
	generateBatch() = MIN .+ (MAX - MIN)*rand(SMatrix{3,POINTS_PER_BATCH,Float64})

	out = Vector{Plane}(undef,BATCHES_COUNT) # initialize output
	for i in eachindex(out)
		b = generateBatch()
		out[i] = best_fit_plane(b)
	end
	return out
end

```

I tried speeding up the code by storing the batch data in a `SMatrix` from `StaticArrays` but it seems that they use the same SVD as in julia base. So no significant speed up. The generation of the batch data is slightly faster though.

---

<div class="post-metadata">

### Author: ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)
#### Post date: [October 29, 2020, 9:45am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/18 "2020-10-29T09:45:07Z")

</div>

@Yury_Kotlyarov if you are working with computational geometry, consider [Meshes.jl](https://github.com/JuliaGeometry/Meshes.jl). It should be a performant api for geometrical objects and meshes. Your Point type is available there for example, as well as many other types.

---

<div class="post-metadata">

### Author: ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)
#### Post date: [October 29, 2020, 9:47am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/19 "2020-10-29T09:47:08Z")

</div>

> [@DNF](#):
>
> If you want more speed-ups, you should consider whether you can do better than the standard `svd` .

For data with only 3 dimensions I think it will be hard to make it faster, otherwise truncated svd or RandomizedLinAlg might be worth a try. Using MKL and `Float32` instead of `Float64` can speed up svd quite a bit.

---

<div class="post-metadata">

### Author: ![moeddel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/moeddel/32/18641_2.png) [@moeddel](https://discourse.julialang.org/u/moeddel)
#### Post date: [October 29, 2020, 10:02am UTC](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223/20 "2020-10-29T10:02:18Z")

</div>

Since we are only interested in one part of the SVD, i.e. U, we can calculate the SVD of MM’ like shown [here](https://www.rdocumentation.org/packages/corpcor/versions/1.6.9/topics/fast.svd). The SVDs of MM’ and M share U.

```julia
function best_fit_plane(points::SMatrix)
    centroid, centered_points = mean_center(points)
	m = centered_points*transpose(centered_points)
    u, = svd(m) # you can leave _, _ if you are interested in the first output only
    Plane(centroid, u[:,end])
end

```

The gain is noticeably, but should be even higher if we increase `POINTS_PER_BATCH`.

```julia
function best_fit_plane(points::SMatrix)
    centroid, centered_points = mean_center(points)
	m = centered_points*transpose(centered_points)
    u, = svd(m) # you can leave _, _ if you are interested in the first output only
    Plane(centroid, u[:,end])
end

```

One needs to check that I have made no errors in the implementation or my train of thought, but with this trick I can bring down the time of the main loop from 15s to 4s using

```julia
BATCHES_COUNT = 250000
POINTS_PER_BATCH = 32

```

[Next page](https://discourse.julialang.org/t/very-slow-execution-time-in-comparison-even-to-python/49223.md?page=2)
