Rust 1.26がコードを3倍にアップグレードする方法

LLVMのパワーと、アセンブラーに対する高レベル言語の利点についての短い話を共有したいと思います。



私はParity Technologiesで働いており、Parity Ethereum clientをサポートしています 。 このクライアントでは、ハードウェアでサポートされているハードウェアがないため、ソフトウェアレベルでエミュレートする必要がある高速256ビット演算が必要です。



長い間、2つの算術実装を並行して行ってきました。1つは安定したアセンブリ用のRustで、もう1つは組み込みアセンブラコード(ナイトリーバージョンのコンパイラで自動的に使用されます)です。 これは、256ビットの数値を64ビットの配列として格納するためですu64



では、2つの64ビットの数値を乗算して64ビットを超える結果を得る方法はありません(Rustの整数型はu64



しか到達しないため)。 これは、x86_64(メインのターゲットプラットフォーム)が64ビットの数値で128ビットの計算結果をネイティブにサポートしているという事実にもかかわらずです。 したがって、各64ビット数を2つの32ビット数に分割します(2つの32ビット数乗算して64ビットの結果を取得できるため)。



 impl U256 { fn full_mul(self, other: Self) -> U512 { let U256(ref me) = self; let U256(ref you) = other; let mut ret = [0u64; U512_SIZE]; for i in 0..U256_SIZE { let mut carry = 0u64; // `split` splits a 64-bit number into upper and lower halves let (b_u, b_l) = split(you[i]); for j in 0..U256_SIZE { // This process is so slow that it's faster to check for 0 and skip // it if possible. if me[j] != 0 || carry != 0 { let a = split(me[j]); // `mul_u32` multiplies a 64-bit number that's been split into // an `(upper, lower)` pair by a 32-bit number to get a 96-bit // result. Yes, 96-bit (it returns a `(u32, u64)` pair). let (c_l, overflow_l) = mul_u32(a, b_l, ret[i + j]); // Since we have to multiply by a 64-bit number, we have to do // this twice. let (c_u, overflow_u) = mul_u32(a, b_u, c_l >> 32); ret[i + j] = (c_l & 0xffffffff) + (c_u << 32); // Then we have to do this complex logic to set the result. Gross. let res = (c_u >> 32) + (overflow_u << 32); let (res, o1) = res.overflowing_add(overflow_l + carry); let (res, o2) = res.overflowing_add(ret[i + j + 1]); ret[i + j + 1] = res; carry = (o1 | o2) as u64; } } } U512(ret) } }
      
      





コードがどれほど最適でないかを知るためにコードを理解する必要さえありません。 コンパイラの出力を確認すると、生成されたアセンブラコードが非常に最適ではないことがわかります。 基本的にRustの制限を回避するために、多くの追加作業を行います。 したがって、組み込みアセンブラーでコードのバージョンを作成しました。 x86_64は、最初に2つの64ビット値の乗算をサポートして128ビットの結果を得るため、アセンブラーコードのバージョンを正確に使用することが重要です。 Rustがa * b



とき、 a



b



u64



形式の場合、プロセッサは実際にそれらを乗算して128ビットの結果を取得し、Rustは上位64ビットをスローします。 これらの64個の上位ビットを残し、それらに効果的にアクセスする唯一の方法は、組み込みアセンブラコードを使用することです。



ご想像のとおり、アセンブリ言語の実装ははるかに高速であることがわかりました。



 名前u64.bench ns / iter inline_asm.bench ns / iter diff ns / iter diff%スピードアップ 
  u256_full_mul 243.159 197.396 -45.763 -18.82%x 1.23 
  u256_mul 268.750 95.843 -172.907 -64.34%x 2.80 
  u256_mul_small 1.608 789-819 -50.93%x 2.04 


u256_full_mul



は上記の関数をチェックし、 u256_mul



は256ビットの結果を得るために2つの256ビットの数値を乗算します(Rustでは512ビットの結果を作成して上半分を破棄しますが、アセンブラーでは別の実装があります)、 u256_mul_small



は2つの小さな256ビットのu256_mul_small



乗算します数字。 ご覧のとおり、アセンブラコードは2.8倍高速です。 それははるかに優れています。 残念なことに、これは夜間バージョンのコンパイラでのみ機能し、x86_64プラットフォームでも機能します。 真実は、アセンブラの「少なくとも」2倍の速度でRustコードを作成するのに多くの努力と多くの失敗した試みが必要だったということです。 必要なデータをコンパイラに渡す良い方法がまったくありませんでした。



Rustバージョン1.26ではすべてが変更されました。 これでa as u128 * b as u128



書くことができます-コンパイラはx86_64プラットフォームにネイティブなu64からu128への乗算を使用します(両方の数値をu128



変換しても、それらは「実際に」すべてu64



であると理解し、結果u128



が必要u128



)。 つまり、コードは次のようになります。



 impl U256 { fn full_mul(self, other: Self) -> U512 { let U256(ref me) = self; let U256(ref you) = other; let mut ret = [0u64; U512_SIZE]; for i in 0..U256_SIZE { let mut carry = 0u64; let b = you[i]; for j in 0..U256_SIZE { let a = me[j]; // This compiles down to just use x86's native 128-bit arithmetic let (hi, low) = split_u128(a as u128 * b as u128); let overflow = { let existing_low = &mut ret[i + j]; let (low, o) = low.overflowing_add(*existing_low); *existing_low = low; o }; carry = { let existing_hi = &mut ret[i + j + 1]; let hi = hi + overflow as u64; let (hi, o0) = hi.overflowing_add(carry); let (hi, o1) = hi.overflowing_add(*existing_hi); *existing_hi = hi; (o0 | o1) as u64 } } } U512(ret) } }
      
      





これはほぼ確実にLLVMのネイティブi256



タイプよりも遅いですが、速度は非常に向上しています。 ここで、元のRust実装と比較します。



 名前u64.bench ns / iter u128.bench ns / iter diff ns / iter diff%スピードアップ 
  u256_full_mul 243.159 73.416 -169.743 -69.81%x 3.31 
  u256_mul 268.750 85.797 -182.953 -68.08%x 3.13 
  u256_mul_small 1.608 558 -1.050 -65.30%x 2.88 


最も注目すべき点は、安定したバージョンのコンパイラで速度が向上したことです。 安定したバージョンのクライアントバイナリのみをコンパイルするため、以前はソースからクライアントをコンパイルしたユーザーのみが速度の利点を得ることができました。 したがって、現在の改善は多くのユーザーに影響を及ぼしています。 しかし、ちょっと、それだけではありません! 256ビットの結果を得るために256ビットの数値を乗算するテストでさえ、アセンブラーでの実装を大幅にマージンを超えてコンパイルした新しいコードです。 これは、Rustコードが最初に512ビットの結果を生成し、次に上半分を破棄し、アセンブリ言語がそれを行わないという事実にもかかわらずです。



 名前inline_asm.bench ns / iter u128.bench ns / iter diff ns / iter diff%スピードアップ 
  u256_full_mul 197.396 73.416 -123.980 -62.81%x 2.69 
  u256_mul 95.843 85.797 -10.046 -10.48%x 1.12 
  u256_mul_small 789558 -231 -29.28%x 1.41 


完全な乗算では、特に元のコードが高度に最適化されたアセンブラーの呪文を使用していたため、これは非常に強力な改善です。 生成されたアセンブラーに飛び込むので、ここでかすかな心が消えます。



これは手書きのアセンブラコードです。 コンパイラーによって実際に作成されたバージョンにコメントしたいので、コメントなしでそれを提示しました(お分かりのように、 asm!



は予想以上に隠れるので):



 impl U256 { /// Multiplies two 256-bit integers to produce full 512-bit integer /// No overflow possible pub fn full_mul(self, other: U256) -> U512 { let self_t: &[u64; 4] = &self.0; let other_t: &[u64; 4] = &other.0; let mut result: [u64; 8] = unsafe { ::core::mem::uninitialized() }; unsafe { asm!(" mov $8, %rax mulq $12 mov %rax, $0 mov %rdx, $1 mov $8, %rax mulq $13 add %rax, $1 adc $$0, %rdx mov %rdx, $2 mov $8, %rax mulq $14 add %rax, $2 adc $$0, %rdx mov %rdx, $3 mov $8, %rax mulq $15 add %rax, $3 adc $$0, %rdx mov %rdx, $4 mov $9, %rax mulq $12 add %rax, $1 adc %rdx, $2 adc $$0, $3 adc $$0, $4 xor $5, $5 adc $$0, $5 xor $6, $6 adc $$0, $6 xor $7, $7 adc $$0, $7 mov $9, %rax mulq $13 add %rax, $2 adc %rdx, $3 adc $$0, $4 adc $$0, $5 adc $$0, $6 adc $$0, $7 mov $9, %rax mulq $14 add %rax, $3 adc %rdx, $4 adc $$0, $5 adc $$0, $6 adc $$0, $7 mov $9, %rax mulq $15 add %rax, $4 adc %rdx, $5 adc $$0, $6 adc $$0, $7 mov $10, %rax mulq $12 add %rax, $2 adc %rdx, $3 adc $$0, $4 adc $$0, $5 adc $$0, $6 adc $$0, $7 mov $10, %rax mulq $13 add %rax, $3 adc %rdx, $4 adc $$0, $5 adc $$0, $6 adc $$0, $7 mov $10, %rax mulq $14 add %rax, $4 adc %rdx, $5 adc $$0, $6 adc $$0, $7 mov $10, %rax mulq $15 add %rax, $5 adc %rdx, $6 adc $$0, $7 mov $11, %rax mulq $12 add %rax, $3 adc %rdx, $4 adc $$0, $5 adc $$0, $6 adc $$0, $7 mov $11, %rax mulq $13 add %rax, $4 adc %rdx, $5 adc $$0, $6 adc $$0, $7 mov $11, %rax mulq $14 add %rax, $5 adc %rdx, $6 adc $$0, $7 mov $11, %rax mulq $15 add %rax, $6 adc %rdx, $7 " : /* $0 */ "={r8}"(result[0]), /* $1 */ "={r9}"(result[1]), /* $2 */ "={r10}"(result[2]), /* $3 */ "={r11}"(result[3]), /* $4 */ "={r12}"(result[4]), /* $5 */ "={r13}"(result[5]), /* $6 */ "={r14}"(result[6]), /* $7 */ "={r15}"(result[7]) : /* $8 */ "m"(self_t[0]), /* $9 */ "m"(self_t[1]), /* $10 */ "m"(self_t[2]), /* $11 */ "m"(self_t[3]), /* $12 */ "m"(other_t[0]), /* $13 */ "m"(other_t[1]), /* $14 */ "m"(other_t[2]), /* $15 */ "m"(other_t[3]) : "rax", "rdx" : ); } U512(result) } }
      
      





そして、それが生成するものです。 人生でアセンブラーを操作したことがない場合でも何が起こるかを理解できるように、コードについて大量にコメントしましたが、メモリとレジスタの違いなど、低レベルプログラミングの基本的な概念を知っておく必要があります。 CPU構造に関するチュートリアルが必要な場合は、プロセッサの構造と実装に関するWikipediaの記事から始めることができます



 bigint::U256::full_mul: ///  -   Rust pushq %r15 pushq %r14 pushq %r13 pushq %r12 subq $0x40, %rsp ///     ... movq 0x68(%rsp), %rax movq 0x70(%rsp), %rcx movq 0x78(%rsp), %rdx movq 0x80(%rsp), %rsi movq 0x88(%rsp), %r8 movq 0x90(%rsp), %r9 movq 0x98(%rsp), %r10 movq 0xa0(%rsp), %r11 /// ...     ///    Rust.   ///  ,      ///       movq %rax, 0x38(%rsp) movq %rcx, 0x30(%rsp) movq %rdx, 0x28(%rsp) movq %rsi, 0x20(%rsp) ///   -  ,   ///     . movq %r8, 0x18(%rsp) movq %r9, 0x10(%rsp) movq %r10, 0x8(%rsp) movq %r11, (%rsp) ///       , ///         . /// /// for i in 0..U256_SIZE { /// for j in 0..U256_SIZE { /// /* Loop body */ /// } /// } ///       ///  "%rax".      ///  `%rax`,     . ///     `asm!`   , ///     . movq 0x38(%rsp), %rax ///      .  ///  ,    ,    . ///  ,   . mulq 0x18(%rsp) /// `mulq`  64-       /// 64    `%rax`  `%rdx`, .  ///    `%r8` ( 64  512- ), ///    `%r9` (  64  ). movq %rax, %r8 movq %rdx, %r9 ///      `i = 0, j = 1` movq 0x38(%rsp), %rax mulq 0x10(%rsp) ///       ,    ///     . addq %rax, %r9 ///    0,   CPU  " " /// (    ) ///   .      , ///   1  `rdx`,    . adcq $0x0, %rdx ///    64   (   ///  )    64  . movq %rdx, %r10 ///    `j = 2`  `j = 3` movq 0x38(%rsp), %rax mulq 0x8(%rsp) addq %rax, %r10 adcq $0x0, %rdx movq %rdx, %r11 movq 0x38(%rsp), %rax mulq (%rsp) addq %rax, %r11 adcq $0x0, %rdx movq %rdx, %r12 ///      `i = 1`, `i = 2`  `i = 3` movq 0x30(%rsp), %rax mulq 0x18(%rsp) addq %rax, %r9 adcq %rdx, %r10 adcq $0x0, %r11 adcq $0x0, %r12 ///  `xor`     `%r13`. ,  ///   (     ),  ///   . xorq %r13, %r13 adcq $0x0, %r13 xorq %r14, %r14 adcq $0x0, %r14 xorq %r15, %r15 adcq $0x0, %r15 movq 0x30(%rsp), %rax mulq 0x10(%rsp) addq %rax, %r10 adcq %rdx, %r11 adcq $0x0, %r12 adcq $0x0, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x30(%rsp), %rax mulq 0x8(%rsp) addq %rax, %r11 adcq %rdx, %r12 adcq $0x0, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x30(%rsp), %rax mulq (%rsp) addq %rax, %r12 adcq %rdx, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x28(%rsp), %rax mulq 0x18(%rsp) addq %rax, %r10 adcq %rdx, %r11 adcq $0x0, %r12 adcq $0x0, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x28(%rsp), %rax mulq 0x10(%rsp) addq %rax, %r11 adcq %rdx, %r12 adcq $0x0, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x28(%rsp), %rax mulq 0x8(%rsp) addq %rax, %r12 adcq %rdx, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x28(%rsp), %rax mulq (%rsp) addq %rax, %r13 adcq %rdx, %r14 adcq $0x0, %r15 movq 0x20(%rsp), %rax mulq 0x18(%rsp) addq %rax, %r11 adcq %rdx, %r12 adcq $0x0, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x20(%rsp), %rax mulq 0x10(%rsp) addq %rax, %r12 adcq %rdx, %r13 adcq $0x0, %r14 adcq $0x0, %r15 movq 0x20(%rsp), %rax mulq 0x8(%rsp) addq %rax, %r13 adcq %rdx, %r14 adcq $0x0, %r15 movq 0x20(%rsp), %rax mulq (%rsp) addq %rax, %r14 adcq %rdx, %r15 ///      ,   ///     movq %r8, (%rdi) movq %r9, 0x8(%rdi) movq %r10, 0x10(%rdi) movq %r11, 0x18(%rdi) movq %r12, 0x20(%rdi) movq %r13, 0x28(%rdi) movq %r14, 0x30(%rdi) movq %r15, 0x38(%rdi) movq %rdi, %rax addq $0x40, %rsp popq %r12 popq %r13 popq %r14 popq %r15 retq
      
      





コメントからわかるように、コードには多くの欠陥があります。 乗算はレジスタからではなくメモリからの変数によって行われ、不必要なストアおよびロード操作が実行されます。また、CPUは「実際の」コードを受信する前に大量のストアおよびロードを強制されます(乗算加算サイクル)。 これは非常に重要です。CPUは計算と並行して保存と読み込みを実行しますが、コードはすべてが読み込まれるまでCPUが計算を開始しないように書かれています。 これは、 asm



マクロが多くの詳細を隠すためです。 基本的に、必要な場所に入力を配置し、文字列操作を使用してアセンブラコードに代入するようにコンパイラに指示します。 コンパイラーはすべてをレジスターに保存しますが、入力配列をメモリー(入力パラメーターの前の"m"



)に入れて、すべてを再びメモリーにロードするように命令します。 このコードを最適化する方法はありますが、経験豊富な専門家でも非常に困難です。 そのようなコードはエラーを起こしやすいです-出力レジスタを一連のxor



命令でリセットしなかった場合、コードは時々失敗しますが、常にではなく、呼び出し関数の内部状態に依存する一見ランダムな値を生成します。 "m"



"r"



置き換えることでおそらく高速化できます(古いアセンブラーコードが非常に遅い理由を記事で確認し始めたときのみ発見したため、テストしませんでした)が、ソースを見ることは明らかではありません。 LLVMアセンブラ構文の深い知識を持つ専門家のみがすぐに理解できます。



比較のために、 u128



を使用したRustコードは非常に明確に見えます:書かれているのはあなたが得るものです。 最適化を目指していなかったとしても、おそらく最も簡単な解決策と似たようなものを書くでしょう。 同時に、LLVMコードは非常に高品質です。 手で書かれたコードとそれほど違わないことに気付くかもしれませんが、いくつかの問題を解決し(以下で説明)、また私が考えもしない最適化をいくつか含んでいます。 彼が見落としていた重要な最適化は見つかりませんでした。



生成されたアセンブラコードは次のとおりです。



 bigint::U256::full_mul: ///  pushq %rbp movq %rsp, %rbp pushq %r15 pushq %r14 pushq %r13 pushq %r12 pushq %rbx subq $0x48, %rsp movq 0x10(%rbp), %r11 movq 0x18(%rbp), %rsi movq %rsi, -0x38(%rbp) ///   ,    , ///       ( /// `movq 0x30(%rbp), %rax`),    `%rax`  ///  `mulq`.  ,     ///        ///      , ///       . movq 0x30(%rbp), %rcx movq %rcx, %rax /// LLVM   ,     mulq %r11 /// LLVM  `%rdx` ( )  ,  ///   .  `%rax` /// ( )   ,   ///       .  , ///       ,    ///   . movq %rdx, %r9 movq %rax, -0x70(%rbp) movq %rcx, %rax mulq %rsi movq %rax, %rbx movq %rdx, %r8 movq 0x20(%rbp), %rsi movq %rcx, %rax mulq %rsi /// LLVM   `%r13`  ,   ///   `%r13`    . movq %rsi, %r13 movq %r13, -0x40(%rbp) /// ,     ,    , ///   LLVM     . movq %rax, %r10 movq %rdx, %r14 movq 0x28(%rbp), %rdx movq %rdx, -0x48(%rbp) movq %rcx, %rax mulq %rdx movq %rax, %r12 movq %rdx, -0x58(%rbp) movq 0x38(%rbp), %r15 movq %r15, %rax mulq %r11 addq %r9, %rbx adcq %r8, %r10 ///        `%rcx` pushfq popq %rcx addq %rax, %rbx movq %rbx, -0x68(%rbp) adcq %rdx, %r10 ///       ///  `%r8`. pushfq popq %r8 /// LLVM     `%rcx`,   ///     .  . ///         , ///        ///      . /// /// ,  LLVM     /// ,     `%rcx` , ///         /// (    `popq %rcx`    /// `pushq %rcx`,    ).    /// ,   . pushq %rcx popfq adcq %r14, %r12 pushfq popq %rax movq %rax, -0x50(%rbp) movq %r15, %rax movq -0x38(%rbp), %rsi mulq %rsi movq %rdx, %rbx movq %rax, %r9 addq %r10, %r9 adcq $0x0, %rbx pushq %r8 popfq adcq $0x0, %rbx /// `setb`      ///     . `setb`  ///   1   ,   ///   (    `mov`,   ///      ) setb -0x29(%rbp) addq %r12, %rbx setb %r10b movq %r15, %rax mulq %r13 movq %rax, %r12 movq %rdx, %r8 movq 0x40(%rbp), %r14 movq %r14, %rax mulq %r11 movq %rdx, %r13 movq %rax, %rcx movq %r14, %rax mulq %rsi movq %rdx, %rsi addq %r9, %rcx movq %rcx, -0x60(%rbp) ///       `%r12`  `%rbx`   ///   `%rcx`.     , ///      . `leaq`  ///   ,       /// ,      `&((void*)first)[second]`  /// `first + second`  C.     .  ///   -  . leaq (%r12,%rbx), %rcx ///       ,  ///  . adcq %rcx, %r13 pushfq popq %rcx addq %rax, %r13 adcq $0x0, %rsi pushq %rcx popfq adcq $0x0, %rsi setb -0x2a(%rbp) orb -0x29(%rbp), %r10b addq %r12, %rbx movzbl %r10b, %ebx adcq %r8, %rbx setb %al movq -0x50(%rbp), %rcx pushq %rcx popfq adcq -0x58(%rbp), %rbx setb %r8b orb %al, %r8b movq %r15, %rax mulq -0x48(%rbp) movq %rdx, %r12 movq %rax, %rcx addq %rbx, %rcx movzbl %r8b, %eax adcq %rax, %r12 addq %rsi, %rcx setb %r10b movq %r14, %rax mulq -0x40(%rbp) movq %rax, %r8 movq %rdx, %rsi movq 0x48(%rbp), %r15 movq %r15, %rax mulq %r11 movq %rdx, %r9 movq %rax, %r11 movq %r15, %rax mulq -0x38(%rbp) movq %rdx, %rbx addq %r13, %r11 leaq (%r8,%rcx), %rdx adcq %rdx, %r9 pushfq popq %rdx addq %rax, %r9 adcq $0x0, %rbx pushq %rdx popfq adcq $0x0, %rbx setb %r13b orb -0x2a(%rbp), %r10b addq %r8, %rcx movzbl %r10b, %ecx adcq %rsi, %rcx setb %al addq %r12, %rcx setb %r8b orb %al, %r8b movq %r14, %rax movq -0x48(%rbp), %r14 mulq %r14 movq %rdx, %r10 movq %rax, %rsi addq %rcx, %rsi movzbl %r8b, %eax adcq %rax, %r10 addq %rbx, %rsi setb %cl orb %r13b, %cl movq %r15, %rax mulq -0x40(%rbp) movq %rdx, %rbx movq %rax, %r8 addq %rsi, %r8 movzbl %cl, %eax adcq %rax, %rbx setb %al addq %r10, %rbx setb %cl orb %al, %cl movq %r15, %rax mulq %r14 addq %rbx, %rax movzbl %cl, %ecx adcq %rcx, %rdx movq -0x70(%rbp), %rcx movq %rcx, (%rdi) movq -0x68(%rbp), %rcx movq %rcx, 0x8(%rdi) movq -0x60(%rbp), %rcx movq %rcx, 0x10(%rdi) movq %r11, 0x18(%rdi) movq %r9, 0x20(%rdi) movq %r8, 0x28(%rdi) movq %rax, 0x30(%rdi) movq %rdx, 0x38(%rdi) movq %rdi, %rax addq $0x48, %rsp popq %rbx popq %r12 popq %r13 popq %r14 popq %r15 popq %rbp retq
      
      





生成されたLLVMバージョンにはさらにいくつかの命令がありますが、最も遅いディレクティブ(ロードとストア)の数は最小限に抑えられます。 ほとんどの場合、LLVMは冗長な作業を回避し、多くの大胆な最適化も適用します。 その結果、コードははるかに高速に実行されます。



慎重に作成されたRust実装がビルドコードを超えたのはこれが初めてではありません。 数か月前、Rustでの加算と減算の実装を書き直し、アセンブリ言語に比べてそれぞれ20%と15%加速しました。 そこでは、 u64:: checked_add/ checked_sub



コードを超えるために128ビット演算は必要ありませんでした(Rustで完全なハードウェアサポートを使用するには、 u64:: checked_add/ checked_sub



を指定するだけです)。



ここでこの例からのコードを見ることができます 、そして、 ここで加算/減算を実装するためのコードを見ることができます 。 後者はすでにアセンブラーでの実装よりも乗算の優位性をベンチマークで示していますが、これは実際にはほとんどの場合ベンチマークが0による乗算を実行したという事実によるものです。 これから教訓が得られる場合、この情報に基づいた最適化は、代表的なベンチマークなしでは不可能です。



LLVMの最適化方法を勉強し、アセンブラコードのバージョンを手動で書き直す必要はないと思います。重要なのは、コンパイラの最適化はすでに本当に優れいるということです。彼らは非常に賢い人によって書かれており、コンピューターは、人々がそれをかなり難しいと感じる最適化のタイプ(数学的な意味で)で本当に優れています。言語開発者の仕事は、オプティマイザーに可能な限り完全に通知するために必要なツールを提供することであり、真の意図は何か、そしてより大きな整数サイズはこれに向けた別のステップです。Rustは、プログラマーが人とコンパイラーの両方にとって理解しやすいプログラムを作成するための素晴らしい仕事をしてきました。主に彼の成功を決定したのはこの力でした。



All Articles