【问题标题】:Recursive Fibonacci in text WebAssembly文本 WebAssembly 中的递归斐波那契
【发布时间】:2018-11-21 06:34:13
【问题描述】:

我一直在玩文本 WebAssembly,想写一个递归斐波那契数计算器。

我得到了一个可以工作的版本,但它使用单个分支 if 语句来检查基本情况:

(module
 (export "fib" (func $fib))
  (func $fib (param $0 i32) (result i32)
  (if
   (i32.lt_s
    (get_local $0)
    (i32.const 2)
   )
   (return
    (i32.const 1)
   )
  )
  (return
   (i32.add
    (call $fib
    (i32.sub
      (get_local $0)
      (i32.const 2)
    )
   )
    (call $fib
      (i32.sub
       (get_local $0)
       (i32.const 1)
     )
    )
   )
  )
 )
) 

我在 wabt 中对此进行了测试:https://webassembly.github.io/wabt/demo/wat2wasm/

我尝试使用 select conditional 重写它:

(module
       (export "fib" (func $fib))
     (func $fib (param $0 i32) (result i32)
            (select
                   (return
                    (i32.const 1)
                    )
                  (return
                   (i32.add
                    (call $fib
                          (i32.sub
                           (get_local $0)
                           (i32.const 2)
                           )
                          )
                    (call $fib
                          (i32.sub
                           (get_local $0)
                           (i32.const 1)
                           )
                          )
                    )
                   )
                (i32.lt_s
                 (get_local $0)
                 (i32.const 2))))
     )

这编译为 .wasm,但它没有按预期运行,只是返回基本情况。我用 if-then-else 尝试了类似的版本,但无济于事。为什么单分支的结果与双分支条件的结果不同?

【问题讨论】:

    标签: recursion webassembly


    【解决方案1】:

    你只是给了我一个学习 wasm 的理由。我还不能回答你关于单分支if 的问题,但我可以向你展示一个有效的fib 函数。

    (module
      (func $fib2 (param $n i32) (param $a i32) (param $b i32) (result i32)
        (if (result i32)
            (i32.eqz (get_local $n))
            (then (get_local $a))
            (else (call $fib2 (i32.sub (get_local $n)
                                       (i32.const 1))
                              (get_local $b)
                              (i32.add (get_local $a)
                                       (get_local $b))))))
    
      (func $fib (param i32) (result i32)
        (call $fib2 (get_local 0)
                    (i32.const 0)   ;; seed value $a
                    (i32.const 1))) ;; seed value $b
    
      (export "fib" (func $fib)))
    

    复制/粘贴来演示它here

    const wasmInstance =
      new WebAssembly.Instance (wasmModule, {})
    
    const { fib } =
      wasmInstance.exports
    
    for (let x = 0; x < 10; x = x + 1)
      console.log (fib (x))
    

    输出

    0
    1
    1
    2
    3
    5
    8
    13
    21
    34
    

    不管怎样,这里的实现与您的完全不同。您的程序需要指数级的计算时间和空间,而上述程序的要求是线性的。

    通过检查您对(call $fib ...) 的使用可以明显看出这一点。在您的程序中,对$fib 的一次调用有可能产生两次$fib 的额外调用,每个调用都有可能产生两次 更多对@ 的调用987654331@,等等。以上$fib2 最多只能调用自己一次


    虽然性能稍逊一些,但实现指数过程当然还是可能

    (module
      (func $fib (param $n i32) (result i32)
        (if (result i32)
            (i32.lt_s (get_local $n)
                      (i32.const 2))
            (then (get_local $n))
            ;; recursive branch spawns _two_ calls to $fib; not ideal
            (else (i32.add (call $fib (i32.sub (get_local $n)
                                               (i32.const 1)))
                           (call $fib (i32.sub (get_local $n)
                                               (i32.const 2)))))))
    
      (export "fib" (func $fib)))
    

    【讨论】:

    • 感谢您提供的工作示例。我想知道将文本 WASM 编译为字节码 WASM 失败的原因是否是某种不喜欢低效递归的编译器优化。坚持暴力递归的原因是试图为 WASM 制作一个玩具转译器。让我感到困惑的是可以编写条件的多种方式以及如何将括号应用于 AST 节点。
    • 我发现Writing WebAssembly By Hand 很有帮助。我还更新了我的答案。这是一种简洁的小语言。感谢您分享您的问题:D
    猜你喜欢
    • 2010-12-03
    • 2014-04-02
    • 2013-06-12
    • 2017-12-12
    • 2017-09-10
    • 2019-04-23
    • 2017-11-18
    • 2014-01-08
    相关资源
    最近更新 更多