Truncate string in Julia - string

Is there a convenience function for truncating strings to a certain length?
It would equivalent to something like this
test_str = "test"
if length(test_str) > 8
out_str = test_str[1:8]
else
out_str = test_str
end

In the naive ASCII world:
truncate_ascii(s,n) = s[1:min(sizeof(s),n)]
would do. If it's preferable to share memory with original string and avoid copying SubString can be used:
truncate_ascii(s,n) = SubString(s,1,min(sizeof(s),n))
But in a Unicode world (and it is a Unicode world) this is better:
truncate_utf8(s,n) = SubString(s,1, (eo=endof(s) ; neo=0 ;
for i=1:n
if neo<eo neo=nextind(s,neo) ; else break ; end ;
end ; neo) )
Finally, #IsmaelVenegasCastelló reminded us of grapheme complexity (arrrgh), and then this is what's needed:
function truncate_grapheme(s,n)
eo = endof(s) ; tt = 0 ; neo=0
for i=1:n
if (neo<eo)
tt = nextind(s,neo)
while neo>0 && tt<eo && !Base.UTF8proc.isgraphemebreak(s[neo],s[tt])
(neo,tt) = (tt,nextind(s,tt))
end
neo = tt
else
break
end
end
return SubString(s,1,neo)
end
These last two implementations try to avoid calculating the length (which can be slow) or allocating/copying, or even just looping n times when the length is shorter.
This answer draws on contributions of #MichaelOhlrogge, #FengyangWang, #Oxinabox and #IsmaelVenegasCastelló

I would do strtruncate(str, n) = join(take(str, n)).
Example:
julia> strtruncate("αβγδ", 3)
"αβγ"
julia> strtruncate("αβγδ", 5)
"αβγδ"
Note that your code is not fully valid for Unicode strings.

If the string is ASCII, this is pretty efficient:
String(resize!(str.data, n))
Or in-place:
resize!(str.data, n)
For unicode, #Fengyang Wangs's method is very fast, but converting to a Char array can be slightly faster if you only truncate the very end of the string:
trunc1(str::String, n) = String(collect(take(str, n)))
trunc2(str::String, n) = String(Vector{Char}(str)[1:n])
trunc3(str::String, n) = String(resize!(Vector{Char}(str), n))
trunc4(str::String, n::Int)::String = join(collect(graphemes(str))[1:n])
function trunc5(str::String, n)
if isascii(str)
return String(resize!(str.data, n))
else
trunc1(str, n)
end
end
Timing:
julia> time_trunc(100, 100000, 25)
0.112851 seconds (700.00 k allocations: 42.725 MB, 7.75% gc time)
0.165806 seconds (700.00 k allocations: 91.553 MB, 11.84% gc time)
0.160116 seconds (600.00 k allocations: 73.242 MB, 11.58% gc time)
1.167706 seconds (31.60 M allocations: 1.049 GB, 11.12% gc time)
0.017833 seconds (100.00 k allocations: 1.526 MB)
true
julia> time_trunc(100, 100000, 98)
0.367191 seconds (700.00 k allocations: 83.923 MB, 5.23% gc time)
0.318507 seconds (700.00 k allocations: 132.751 MB, 9.08% gc time)
0.301685 seconds (600.00 k allocations: 80.872 MB, 6.19% gc time)
1.561337 seconds (31.80 M allocations: 1.122 GB, 9.86% gc time)
0.061827 seconds (100.00 k allocations: 1.526 MB)
true
Edit: Whoops.. I just realized that I'm actually destroying the original string in trunc5. This should be correct, but with less superior performance:
function trunc5(str::String, n)
if isascii(str)
return String(str.data[1:n])
else
trunc1(str, n)
end
end
New timings:
julia> time_trunc(100, 100000, 25)
0.123629 seconds (700.00 k allocations: 42.725 MB, 7.70% gc time)
0.162332 seconds (700.00 k allocations: 91.553 MB, 11.41% gc time)
0.152473 seconds (600.00 k allocations: 73.242 MB, 9.19% gc time)
1.152640 seconds (31.60 M allocations: 1.049 GB, 11.54% gc time)
0.066662 seconds (200.00 k allocations: 12.207 MB)
true
julia> time_trunc(100, 100000, 98)
0.369576 seconds (700.00 k allocations: 83.923 MB, 5.10% gc time)
0.312237 seconds (700.00 k allocations: 132.751 MB, 9.42% gc time)
0.297736 seconds (600.00 k allocations: 80.872 MB, 5.95% gc time)
1.545329 seconds (31.80 M allocations: 1.122 GB, 10.02% gc time)
0.080399 seconds (200.00 k allocations: 19.836 MB, 5.07% gc time)
true
Aaand new edit: Aargh, forgot the timing function. I'm inputting an ascii string:
function time_trunc(m, n, m_)
str = randstring(m)
#time for _ in 1:n trunc1(str, m_) end
#time for _ in 1:n trunc2(str, m_) end
#time for _ in 1:n trunc3(str, m_) end
#time for _ in 1:n trunc4(str, m_) end
#time for _ in 1:n trunc5(str, m_) end
trunc1(str, m_) == trunc2(str, m_) == trunc3(str, m_) == trunc4(str, m_) == trunc5(str, m_)
end
Final edit (I hope):
Trying out #Dan Getz's truncate_grapheme and using unicode strings:
function time_trunc(m, n, m_)
# str = randstring(m)
str = join(["αβγπϕ1t_Ω₃!" for i in 1:100])
#time for _ in 1:n trunc1(str, m_) end
#time for _ in 1:n trunc2(str, m_) end
#time for _ in 1:n trunc3(str, m_) end
# #time for _ in 1:n trunc4(str, m_) end # too slow
#time for _ in 1:n trunc5(str, m_) end
#time for _ in 1:n truncate_grapheme(str, m_) end
trunc1(str, m_) == trunc2(str, m_) == trunc3(str, m_) == trunc5(str, m_) == truncate_grapheme(str, m_)
end
Timing:
julia> time_trunc(100, 100000, 98)
0.690399 seconds (800.00 k allocations: 103.760 MB, 3.69% gc time)
1.828437 seconds (800.00 k allocations: 534.058 MB, 3.66% gc time)
1.795005 seconds (700.00 k allocations: 482.178 MB, 3.19% gc time)
0.667831 seconds (800.00 k allocations: 103.760 MB, 3.17% gc time)
0.347953 seconds (100.00 k allocations: 3.052 MB)
true
julia> time_trunc(100, 100000, 25)
0.282922 seconds (800.00 k allocations: 48.828 MB, 4.01% gc time)
1.576374 seconds (800.00 k allocations: 479.126 MB, 3.98% gc time)
1.643700 seconds (700.00 k allocations: 460.815 MB, 3.70% gc time)
0.276586 seconds (800.00 k allocations: 48.828 MB, 4.59% gc time)
0.091773 seconds (100.00 k allocations: 3.052 MB)
true
So the last one seems clearly the best (and this post is now way too long.)

You could use the graphemes function:
C:\Users\Ismael
λ julia5
_
_ _ _(_)_ | By greedy hackers for greedy hackers.
(_) | (_) (_) | Documentation: http://docs.julialang.org
_ _ _| |_ __ _ | Type "?help" for help.
| | | | | | |/ _' | |
| | |_| | | | (_| | | Version 0.5.0-rc3+0 (2016-08-22 23:43 UTC)
_/ |\__'_|_|_|\__'_| | Official http://julialang.org/ release
|__/ | x86_64-w64-mingw32
help?> graphemes
search: graphemes
graphemes(s) -> iterator over substrings of s
Returns an iterator over substrings of s that correspond to the extended
graphemes in the string, as defined by Unicode UAX #29.
(Roughly, these are what users would perceive as single characters, even
though they may contain more than one codepoint; for example a letter
combined with an accent mark is a single grapheme.)
Example:
julia> s = "αβγπϕ1t_Ω₃!"; n = 8;
julia> length(s)
11
julia> graphemes(s)
length-11 GraphemeIterator{String} for "αβγπϕ1t_Ω₃!"
julia> collect(ans)[1:n]
8-element Array{SubString{String},1}:
"α"
"β"
"γ"
"π"
"ϕ"
"1"
"t"
"_"
julia> join(ans)
"αβγπϕ1t_"
Check out the truncate function:
julia> methods(truncate)
# 2 methods for generic function "truncate":
truncate(s::IOStream, n::Integer) at iostream.jl:43
truncate(io::Base.AbstractIOBuffer, n::Integer) at iobuffer.jl:140
help?> truncate
search: truncate
truncate(file,n)
Resize the file or buffer given by the first argument to exactly n bytes,
filling previously unallocated space with '\0' if the file or buffer is
grown.
So the solution could look like this:
julia> #doc """
truncate(s::String, n::Int)::String
truncate a `String`; `s` up to `n` graphemes.
# Example
```julia
julia> truncate("αβγπϕ1t_Ω₃!", 8)
"αβγπϕ1t_"
julia> truncate("test", 8)
"test"
```
""" ->
function Base.truncate(s::String, n::Int)::String
if length(s) > n
join(collect(graphemes(s))[1:n])
else
s
end
end
Base.truncate
Test it:
julia> methods(truncate)
# 3 methods for generic function "truncate":
truncate(s::String, n::Int64)
truncate(s::IOStream, n::Integer) at iostream.jl:43
truncate(io::Base.AbstractIOBuffer, n::Integer) at iobuffer.jl:140
help?> truncate
truncate(file,n)
Resize the file or buffer given by the first argument to exactly n bytes,
filling previously unallocated space with '\0' if the file or buffer is
grown.
truncate(s::String, n::Int)::String
truncate a String; s up to n graphemes.
Example
≡≡≡≡≡≡≡≡≡
julia> truncate("αβγπϕ1t_Ω₃!", 8)
"αβγπϕ1t_"
julia> truncate("test", 8)
"test"
julia> truncate("αβγπϕ1t_Ω₃!", n)
"αβγπϕ1t_"
julia> truncate("test", n)
"test"
Profile it:
julia> Pkg.add("BenchmarkTools")
INFO: Nothing to be done
INFO: METADATA is out-of-date — you may not have the latest version of BenchmarkTools
INFO: Use `Pkg.update()` to get the latest versions of your packages
julia> using BenchmarkTools
julia> #benchmark truncate("αβγπϕ1t_Ω₃!", 8)
BenchmarkTools.Trial:
samples: 10000
evals/sample: 9
time tolerance: 5.00%
memory tolerance: 1.00%
memory estimate: 1.72 kb
allocs estimate: 48
minimum time: 1.96 μs (0.00% GC)
median time: 2.10 μs (0.00% GC)
mean time: 2.45 μs (7.80% GC)
maximum time: 353.75 μs (98.40% GC)
julia> Sys.cpu_info()[]
Intel(R) Core(TM) i7-4710HQ CPU # 2.50GHz:
speed user nice sys idle irq ticks
2494 MHz 937640 0 762890 11104468 144671 ticks

You could use:
"test"[1:min(end,8)]
Also
SubString("test", 1, 8)

Here's one that can handle any UTF-8 string:
function trim_str(str, max_length)
edge = nextind(str, 0, max_length)
if edge >= ncodeunits(str)
str
else
str[1:edge]
end
end

Related

Improve loops performance with parallelization

So I'm trying to wrap my head around Julia's parallelization options. I'm modelling stochastic processes as Markov chains. Since the chains are independent replicates, the outer loops are independent - making the problem embarrassingly parallel.
I tried to implement both a #distributed and a #threads solution, both of which seem to run fine, but aren't any faster than the sequential.
Here's a simplified version of my code (sequential):
function dummy(steps = 10000, width = 100, chains = 4)
out_N = zeros(steps, width, chains)
initial = zeros(width)
for c = 1:chains
# print("c=$c\n")
N = zeros(steps, width)
state = copy(initial)
N[1,:] = state
for i = 1:steps
state = state + rand(width)
N[i,:] = state
end
out_N[:,:,c] = N
end
return out_N
end
What would be the correct way of parallelizing this problem to increase performance?
Here is the correct way to do it (at the time of writing this answer the other answer does not work - see my comment).
I will use slightly less complex example than in the question (however very similar).
1. Not parallelized version (baseline scenario)
using Random
const m = MersenneTwister(0);
function dothestuff!(out_N, N, ic, m)
out_N[:, ic] .= rand(m, N)
end
function dummy_base(m=m, N=100_000,c=256)
out_N = Array{Float64}(undef,N,c)
for ic in 1:c
dothestuff!(out_N, N, ic, m)
end
out_N
end
Testing:
julia> using BenchmarkTools; #btime dummy_base();
106.512 ms (514 allocations: 390.64 MiB)
2. Parallelize with threads
#remember to run before starting Julia:
# set JULIA_NUM_THREADS=4
# OR (Linux)
# export JULIA_NUM_THREADS=4
using Random
const mt = MersenneTwister.(1:Threads.nthreads());
# required for older Julia versions, look still good in later versions :-)
function dothestuff!(out_N, N, ic, m)
out_N[:, ic] .= rand(m, N)
end
function dummy_threads(mt=mt, N=100_000,c=256)
out_N = Array{Float64}(undef,N,c)
Threads.#threads for ic in 1:c
dothestuff!(out_N, N, ic, mt[Threads.threadid()])
end
out_N
end
Let us test the performance:
julia> using BenchmarkTools; #btime dummy_threads();
46.775 ms (535 allocations: 390.65 MiB)
3. Parallelize with processes (on a single machine)
using Distributed
addprocs(4)
using Random, SharedArrays
#everywhere using Random, SharedArrays, Distributed
#everywhere Random.seed!(myid())
#everywhere function dothestuff!(out_N, N, ic)
out_N[:, ic] .= rand(N)
end
function dummy_distr(N=100_000,c=256)
out_N = SharedArray{Float64}(N,c)
#sync #distributed for ic in 1:c
dothestuff!(out_N, N, ic)
end
out_N
end
Performance (note that inter-process communication takes some time and hence for small computations threads will be usually better):
julia> using BenchmarkTools; #btime dummy_distr();
62.584 ms (1073 allocations: 45.48 KiB)
You can use #distributed macro, to run processes in parallel
#everywhere using Distributed, SharedArrays
addprocs(4)
#everywhere function inner_loop!(out_N, chain_number,steps,width)
N = zeros(steps, width)
state = zeros(width)
for i = 1:steps
state .+= rand(width)
N[i,:] .= state
end
out_N[:,:,chain_number] .= N
nothing
end
function dummy(steps = 10000, width = 100, chains = 4)
out_N = SharedArray{Float64}((steps, width, chains); pids = collect(1:4))
#sync for c = 1:chains
# print("c=$c\n")
#spawnat :any inner_loop!(out_N, c, steps,width)
end
sdata(out_N)
end

Julia: how to get a random permutation of a given string s?

I thought about two different ways, but both seem pretty ugly.
Transform the string s into an array a by splitting it, then use sample(a, length(s), replace=false) and join the array again into a string
Get a RandomPermutation r of length length(s) and join the single s[i] for i in r.
What's the right way? Unfortunately there is no method matching sample(::String, ::Int64; replace=false).
Perhaps defining a shuffle method for String constitutes type piracy, but, anyway, here's a suggested implemetation:
Base.shuffle(s::String) = isascii(s) ? s[randperm(end)] : join(shuffle!(collect(s)))
If you wanted to squeeze out performance from shuffle then you can consider:
function shufflefast(s::String)
ss = sizeof(s)
l = length(s)
ss == l && return String(shuffle!(copy(Vector{UInt8}(s))))
v = Vector{Int}(l)
i = start(s)
for j in 1:l
v[j] = i
i = nextind(s, i)
end
p = pointer(s)
u = Vector{UInt8}(ss)
k = 1
for i in randperm(l)
for j in v[i]:(i == l ? ss : v[i+1]-1)
u[k] = unsafe_load(p, j)
k += 1
end
end
String(u)
end
For large strings it is over 4x faster for ASCII and 3x faster for UTF-8.
Unfortunately it is messy - so I would rather treat it as an exercise. However, it uses only exported functions so it is not a hack.
Inspired by the optimization tricks in Bogumil Kaminski's answer, the following is a version with almost the same performance, but a bit clearer (in my opinion) and using a second utility function which may be of value in itself:
function strranges(s) # returns the ranges of bytes spanned by chars
u = Vector{UnitRange{Int64}}()
sizehint!(u,sizeof(s))
i = 1
while i<=sizeof(s)
ii = nextind(s,i)
push!(u,i:ii-1)
i = ii
end
return u
end
function shufflefast(s)
ss = convert(Vector{UInt8},s)
uu = Vector{UInt8}(length(ss))
i = 1
#inbounds for r in shuffle!(strranges(s))
for j in r
uu[i] = ss[j]
i += 1
end
end
return String(uu)
end
Example timing:
julia> using BenchmarkTools
julia> s = "ďaľšý"
julia> #btime shuffle($s) # shuffle from DNF's answer
831.200 ns (9 allocations: 416 bytes)
"ýľďša"
julia> #btime shufflefast($s) # shuffle from this answer
252.224 ns (5 allocations: 432 bytes)
"ľýďaš"
julia> #btime kaminskishufflefast($s) # shuffle from Kaminski's answer
197.345 ns (4 allocations: 384 bytes)
"ýašďľ"
EDIT: a little better performance - see code comments
This is from Bogumil Kaminski's answer where I am trying to avoid calculating length (*) if it is not necessary:
function shufflefast2(s::String)
ss = sizeof(s)
local l
for l in 1:ss
#if ((codeunit(s,l) & 0xc0) == 0x80)
if codeunit(s,l)>= 0x80 # edit (see comments bellow why)
break
end
end
ss == l && return String(shuffle!(copy(Vector{UInt8}(s))))
v = Vector{Int}(ss)
i = 1
l = 0
while i<ss
l += 1
v[l] = i
i = nextind(s, i)
end
v[l+1] = ss+1 # edit - we could do this because ss>l
p = pointer(s)
u = Vector{UInt8}(ss)
k = 1
for i in randperm(l)
# for j in v[i]:(i == l ? ss : v[i+1]-1)
for j in v[i]:v[i+1]-1 # edit we could do this because v[l+1] is defined (see above)
u[k] = unsafe_load(p, j)
k += 1
end
end
String(u)
end
Example timing for ascii string:
julia> srand(1234);#btime for i in 1:100 danshufflefast("test") end
19.783 μs (500 allocations: 34.38 KiB)
julia> srand(1234);#btime for i in 1:100 bkshufflefast("test") end
10.408 μs (300 allocations: 18.75 KiB)
julia> srand(1234);#btime for i in 1:100 shufflefast2("test") end
10.280 μs (300 allocations: 18.75 KiB)
Difference is too small, sometimes bkshufflefast is faster. Performance has to be equal. Whole length has to be count and there is same allocation.
Example timing for unicode string:
julia> srand(1234);#btime for i in 1:100 danshufflefast(s) end
24.964 μs (500 allocations: 42.19 KiB)
julia> srand(1234);#btime for i in 1:100 bkshufflefast(s) end
20.882 μs (400 allocations: 37.50 KiB)
julia> srand(1234);#btime for i in 1:100 shufflefast2(s) end
19.038 μs (400 allocations: 40.63 KiB)
shufflefast2 is a little but clearly faster here. A little more allocation than Bogumil's function and a little less allocation than in Dan's solution.
(*) - I a little hope that String implementation in Julia will be faster in future and length could be much quicker than it is now.

Script to extract and find min, max and avg from a file using a shell script from traceroute output

I am creating a shell scrip to store the output of the traceroute command with a user-entered input to a file.
I want to extract the latency for each packet and each router and find the min, max and average times for each packet.
If this is the output of traceroute:
1 176.221.87.1 (176.221.87.1) 1.474 ms 1.444 ms 1.390 ms
2 f126.broadband2.quicknet.se (92.43.37.126) 10.047 ms 19.868 ms 23.156 ms
3 10.5.12.1 (10.5.12.1) 24.098 ms 24.340 ms 25.311 ms
I need to find the max of all latency for the first packet, which is in this case 24.098 ms. Similarly min is 1.474 and average for the first packet is 11.873 ms. I need to do this for each packet.
I want output like:
1 176.221.87.1 (176.221.87.1) 1.474 ms 1.444 ms 1.390 ms
2 f126.broadband2.quicknet.se (92.43.37.126) 10.047 ms 19.868 ms 23.156 ms
3 10.5.12.1 (10.5.12.1) 24.098 ms 24.340 ms 25.311 ms
For the first packet:
Minimum: 1.474 ms
Maximum: 24.098 ms
Average: 11.873 ms
.
.
and so on.
I am not able to come up with an awk statement to do this. Perhaps there is another way?
Any inputs would be really helpful.
If my understanding is correct, you want something like this
NR == 1 {
n = $4 # set min to first value
u = $5 # keep time unit for later
}
{
s += $4
c++
if ($4 > m) { # update max
m = $4
}
if ($4 < n) { # update min
n = $4
}
}
END {
print "Minimum: " n, u "\nMaximum: " m, u "\nAverage: " s / c, u
}
For all three columns, you can extend this easily
NR == 1 {
split("4,6,8",ix,",")
for(i in ix) n[ix[i]] = $(ix[i])
u = $5
}
{
c++
for(i in ix) {
p = ix[i];
s[p] += $(p)
if ($p > m[p]) m[p] = $p
if ($p < n[p]) n[p] = $p
}
}
END {
mins = "Minimum ("u"):"
maxs = "Maximum ("u"):"
avgs = "Average ("u"):"
for(i in ix) {
mins = mins FS n[ix[i]]
maxs = maxs FS m[ix[i]]
avgs = avgs FS s[ix[i]]/c
}
print mins
print maxs
print avgs
}
Should print
Minimum (ms): 1.474 1.444 1.390
Maximum (ms): 24.098 24.340 25.311
Average (ms): 11.873 15.2173 16.619

Algorithm for counting things by the second and maintaining a running average

I want to count requests by certain attributes and summarize them by a certain time period (probably by the second) and then have running averages/max/min for last 10 seconds, last 2 minutes, etc.
The obvious (to me) approach is to just have a list of seconds and when I need the moving/running average then just go back in the list the appropriate amount of time and calculate the average. Other than some obvious optimizations around storing aggregated values to use for the longer time periods, what ideas am I missing?
I prefer exponential moving average as it is simpler and does not require to keep values in array
Here is function I used in past
func MovingExpAvg(value, oldValue, fdtime, ftime float64) float64 {
alpha := 1.0 - math.Exp(-fdtime/ftime)
r := alpha * value + (1.0 - alpha) * oldValue
return r
}
and code example
http://play.golang.org/p/OZ25cwKMnT
I'm not really familiar with Go, so please excuse any strangeness in the following code. Adding an element to the rolling average should be O(1) in time. It uses O(n) in memory (a fixed amount).
package main
import "fmt"
func rolling(n int) func(float64) float64 {
bins := make([]float64, n)
average := 0.0
i := 0
return func(x float64) float64 {
average += (x - bins[i]) / float64(n)
bins[i] = x
i = (i + 1) % n
return average
}
}
func main() {
add := rolling(5)
add(1)
add(2)
add(3)
add(4)
fmt.Println("(1+2+3+4+5 ) / 5 =", add(5))
fmt.Println("( 2+3+4+5+9 ) / 5 =", add(9))
fmt.Println("( 3+4+5+9+3 ) / 5 =", add(3))
fmt.Println("( 4+5+9+3+0 ) / 5 =", add(0))
fmt.Println("( 5+9+3+0-9 ) / 5 =", add(-9))
fmt.Println("( 9+3+0-9-8) / 5 =", add(-8))
}
Output:
$ go run roll.go
(1+2+3+4+5 ) / 5 = 3
( 2+3+4+5+9 ) / 5 = 4.6
( 3+4+5+9+3 ) / 5 = 4.8
( 4+5+9+3+0 ) / 5 = 4.2
( 5+9+3+0-9 ) / 5 = 1.6
( 9+3+0-9-8) / 5 = -1

The more threads that changes the Clojure's ref are, the more does the rate of retries per threads rise?

I worry about this a little.
Imagine the simplest version controll way that programmers just copy all directory from the master repository and after changing a file do reversely if the master repository is still the same. If it has been changed by another, they must try again.
When the number of programmers increases, it is natural that retries also increase, but it might not be proportional to the number of programmers.
If ten programmers work and a work takes an hour per person, to complete all work ten hours are needed at least.
If they are earnest, about 9 + 8 + 7 + ... 1 = 45 man-hours come to nothing.
In a hundread of programmers, about 99 + 98 + ... 1 = 4950 man-hours come to nothing.
I tried to count the number of retries and got the results.
Source
(defn fib [n]
(if (or (zero? n) (= n 1))
1
(+ (fib (dec n) ) (fib (- n 2)))))
(defn calc! [r counter-A counter-B counter-C n]
(dosync
(swap! counter-A inc)
;;(Thread/sleep n)
(fib n)
(swap! counter-B inc)
(alter r inc)
(swap! counter-C inc)))
(defn main [thread-num n]
(let [r (ref 0)
counter-A (atom 0)
counter-B (atom 0)
counter-C (atom 0)]
(doall (pmap deref
(for [_ (take thread-num (repeat nil))]
(future (calc! r counter-A counter-B counter-C n)))))
(println thread-num " Thread. #ref:" #r)
(println "A:" #counter-A ", B:" #counter-B ", C:" #counter-C)))
CPU: 2.93GHz Quad-Core Intel Core i7
result
user> (time (main 10 25))
10 Thread. #ref: 10
A: 53 , B: 53 , C: 10
"Elapsed time: 94.412 msecs"
nil
user> (time (main 100 25))
100 Thread. #ref: 100
A: 545 , B: 545 , C: 100
"Elapsed time: 966.141 msecs"
nil
user> (time (main 1000 25))
1000 Thread. #ref: 1000
A: 5507 , B: 5507 , C: 1000
"Elapsed time: 9555.165 msecs"
nil
I changed the job to (Thread/sleep n) instead of (fib n) and got similar results.
user> (time (main 10 20))
10 Thread. #ref: 10
A: 55 , B: 55 , C: 10
"Elapsed time: 220.616 msecs"
nil
user> (time (main 100 20))
100 Thread. #ref: 100
A: 689 , B: 689 , C: 117
"Elapsed time: 2013.729 msecs"
nil
user> (time (main 1000 20))
1000 Thread. #ref: 1000
A: 6911 , B: 6911 , C: 1127
"Elapsed time: 20243.214 msecs"
nil
In Thread/sleep case, I think retries could increase more than this result because CPU is available.
Why don't retries increase?
Thanks.
Because you are not actually spawning 10, 100 or 1000 threads! Creating a future does not always create a new thread. It uses a thread pool behind the scenes where it keeps queuing the jobs (or Runnables to be technical). The thread pool is a cached thread pool which reuses the threads for running the jobs.
So in your case, you are not actually spawning a 1000 threads. If you want to see the retries in action, get a level below future - create your own thread pool and push Runnables into it.
self answer
I have modified main function not to use pmap and got results, which work out as calculated.
(defn main [thread-num n]
(let [r (ref 0)
counter-A (atom 0)
counter-B (atom 0)
counter-C (atom 0)]
(doall (map deref (doall (for [_ (take thread-num (repeat nil))]
(future (calc! r counter-A counter-B counter-C n))))))
(println thread-num " Thread. #ref:" #r)
(println "A:" #counter-A ", B:" #counter-B ", C:" #counter-C)))
fib
user=> (main 10 25)
10 Thread. #ref: 10
A: 55 , B: 55 , C: 10
nil
user=> (main 100 25)
100 Thread. #ref: 100
A: 1213 , B: 1213 , C: 100
nil
user=> (main 1000 25)
1000 Thread. #ref: 1000
A: 19992 , B: 19992 , C: 1001
nil
Thread/sleep
user=> (main 10 20)
10 Thread. #ref: 10
A: 55 , B: 55 , C: 10
nil
user=> (main 100 20)
100 Thread. #ref: 100
A: 4979 , B: 4979 , C: 102
nil
user=> (main 1000 20)
1000 Thread. #ref: 1000
A: 491223 , B: 491223 , C: 1008
nil

Resources