这个 Crystal 基准测试代码可以显着改进吗?



我正在决定一种用于后端使用的语言。我看过Go,Rust,C++,我想我会看看Crystal,因为它在某些基准测试中表现得相当不错。速度不是最终要求,但速度很重要。语法同样重要,我不反对 Crystal 语法。我写的简单基准只是评估的一小部分,也是部分熟悉。我使用的是Windows,所以我在Win10 2004-19041.329上使用Crystal 0.35.1和WSL2和Ubuntu 20.04 LTS。我不知道 WSL2 是否对性能有任何影响。基准测试主要使用整数算法。Go、Rust 和 C++ 的性能几乎相等(在 Win10 上(。我已经将该代码转换为Crystal,它的运行速度比这三段要慢得多。出于简单的好奇心,我还在 Dart(在 Win10 上(上运行了代码,它的运行速度(非常令人惊讶(几乎是这三个代码的两倍。我确实理解一个简单的基准并不能说明很多问题。我从最近的一篇文章中注意到,Crystal 对浮点数比对整数更有效,但这过去和现在都是针对整数的。

这是我的第一个 Crystal 程序,所以我想我应该问 - 我可以对代码进行任何简单的性能改进吗?除了纠正错误之外,我不想改进算法,因为所有人都在使用此算法。

代码如下:

# ------ Prime-number counter. -----#
# Brian      25-Jun-2020       Program written - my first Crystal program.
# -------- METHOD TO CALCULATE APPROXIMATE SQRT ----------#
def fnCalcSqrt(iCurrVal)  # Calculate approximate sqrt
iPrevDiv = 0.to_i64
iDiv = (iCurrVal // 10)
if iDiv < 2
iDiv = 2
end
while (true)
begin
iProd = (iDiv * iDiv)
rescue vError
puts "Error = #{vError}, iDiv = #{iDiv}, iCurrVal = #{iCurrVal}"
exit
end
if iPrevDiv < iDiv
iDiff = ((iDiv - iPrevDiv) // 2)
else
iDiff = ((iPrevDiv - iDiv) // 2)
end
iPrevDiv = iDiv
if iProd < iCurrVal # iDiv IS TOO LOW #
if iDiff < 1
iDiff = 1
end
iDiv += iDiff
else
if iDiff < 2
return iDiv
end
iDiv -= iDiff
end
end
end
# ---------- PROGRAM MAINLINE --------------#
print "nCalculate Primes from 1 to selected number"
#iMills = uninitialized Int32   # CHANGED THIS BECAUSE IN --release DOES NOT WORK
iMills = 0.to_i32
while iMills < 1 || iMills > 100
print "nEnter the ending number of millions (1 to 100) : "
sInput = gets
if sInput == ""
exit
end
iTemp = sInput.try &.to_i32?
if !iTemp
puts "Please enter a valid number"
puts "iMills = #{iTemp}"
elsif iTemp > 100   # > 100m
puts "Invalid - too big must be from 1 to 100 (million)"
elsif iTemp < 1
puts "Invalid - too small - must be from 1 to 100 (million)"
else
iMills = iTemp
end
end
#iCurrVal = 2   # THIS CAUSES ARITHMETIC OVERFLOW IN SQRT CALC.
iCurrVal = 2.to_i64
iEndVal = iMills * 1_000_000
iPrimeTot = 0
# ----- START OF PRIME NUMBER CALCULATION -----#
sEndVal = iEndVal.format(',', group: 3) # => eg. "10,000,000"
puts "Calculating number of prime numbers from 2 to #{sEndVal} ......"
vStartTime = Time.monotonic
while iCurrVal <= iEndVal
if iCurrVal % 2 != 0 || iCurrVal == 2
iSqrt = fnCalcSqrt(iCurrVal)
tfPrime = true  # INIT
iDiv = 2
while iDiv <= iSqrt
if ((iCurrVal % iDiv) == 0)
tfPrime = (iDiv == iCurrVal);
break;
end
iDiv += 1
end
if (tfPrime)
iPrimeTot+=1;
end
end
iCurrVal += 1
end
puts "Elapsed time = #{Time.monotonic - vStartTime}"
puts "prime total = #{iPrimeTot}"

你需要用--release标志进行编译。默认情况下,Crystal 编译器专注于编译速度,因此您可以快速获得编译程序。这在开发过程中尤其重要。如果你想要一个运行速度快的程序,你需要传递--release标志,它告诉编译器花时间进行优化(顺便说一句,这由LLVM处理(。

您还可以通过在保证结果可以溢出的位置使用包装运算符(如&+(来缩短一些时间。这将跳过一些溢出检查。


其他几点评论:

代替0.to_i64,你可以只使用 Int64 文字:0_i64

iMills = uninitialized Int32

不要在这里使用uninitialized。这是完全错误的。您希望初始化该变量。在 C 绑定中有一些uninitialized用例,还有一些非常具体的低级实现。它永远不应该在大多数常规代码中使用。

我从最近的一篇文章中注意到,Crystal 对浮点数比对整数更有效

你在哪里读到的?

在 Crystal 中,为标识符添加类型前缀似乎不是很有用。编译器已经跟踪了这些类型。作为开发人员,您也不应该这样做。我以前从未见过。

最新更新