Прежде всего /
в Crystal - это деление на числа с плавающей точкой, так что это в значительной степени сравнивает числа с плавающей точкой:
typeof(a) # => Float64
typeof(b) # => Int32
typeof(d) # => Float64 | Int32)
Если мы установим эталонный тест для использования целочисленного деления, //
, я получу:
int32 631.35M ( 1.58ns) (± 5.53%) 0.0B/op 1.23× slower
float64 773.57M ( 1.29ns) (± 3.21%) 0.0B/op fastest
По-прежнему нет реальной разницы в пределах погрешности. Почему это? Давайте копать глубже. Сначала мы можем извлечь примеры в не встроенную функцию и убедиться, что она вызывается так, что Crystal не просто игнорирует это:
@[NoInline]
def calc
a = 128973 // 119236
b = 119236 - 128973
d = 117232 > 123462 ? 117232 * 123462 : 123462 // 117232
a + b + d
end
p calc
Затем мы можем построить это с помощью crystal build --release --no-debug --emit llvm-ir
, чтобы получить .ll
файл с оптимизированным LLVM-IR. Мы выкапываем нашу calc
функцию и видим что-то вроде этого:
define i32 @"*calc:Int32"() local_unnamed_addr #19 {
alloca:
%0 = tail call i1 @llvm.expect.i1(i1 false, i1 false)
br i1 %0, label %overflow, label %normal6
overflow: ; preds = %alloca
tail call void @__crystal_raise_overflow()
unreachable
normal6: ; preds = %alloca
ret i32 -9735
}
Куда делись все наши вычисления? LLVM сделал их во время компиляции, потому что это были все константы! Мы можем повторить эксперимент на примере Float64
:
define double @"*calc:Float64"() local_unnamed_addr #11 {
alloca:
ret double 0x40004CAA3B35919C
}
Немного меньше шаблонного, следовательно, он немного быстрее, но опять же, все предварительно вычислено!
Я закончу упражнение Вот. Дальнейшие исследования для читателя:
- Что произойдет, если мы попытаемся ввести непостоянные термины во все выражения?
- Предполагается ли, что 32-битные целочисленные операции должны выполняться быстрее или медленнее, чем 64-битные IEEE754 операции с плавающей запятой на современном 64-разрядном процессоре в здравом уме?