99re热视频这里只精品,久久久天堂国产精品女人,国产av一区二区三区,久久久精品成人免费看片,99久久精品免费看国产一区二区三区

Rust 嵌入到其他語(yǔ)言

2022-04-21 10:05 更新

Rust 嵌入到其他語(yǔ)言

我們的第三個(gè)項(xiàng)目,我們要選擇展示那些能展示 Rust 最大優(yōu)點(diǎn)的點(diǎn):大量運(yùn)行時(shí)的減少。

隨著我們組織的發(fā)展,其越來(lái)越依賴其他的一些編程語(yǔ)言。不同的編程語(yǔ)言有不同的優(yōu)點(diǎn)和缺點(diǎn),通曉數(shù)種語(yǔ)言的堆棧允許你使用一個(gè)特定的語(yǔ)言,在其的優(yōu)勢(shì)方面,而在其弱勢(shì)的方面,你可以使用另一種語(yǔ)言。

許多程語(yǔ)言一個(gè)共同薄弱的地方就是程序的運(yùn)行時(shí)性能。通常情況下,使用了一種運(yùn)行比較慢的語(yǔ)言,但是如果它同時(shí)能提升程序員的工作效率也是值得的。為了幫助緩解這個(gè)問(wèn)題,他們提供了一個(gè)方法,系統(tǒng)中的一部分用 C 來(lái)寫,然后再調(diào)用 C 代碼,那么這一部分好像就是用高級(jí)語(yǔ)言編寫的似得。這被稱作“外部程序接口”,一般縮寫成“FFI”。

Rust 在兩個(gè)方面上支持 FFI:它可以容易的調(diào)用 C 代碼,但至關(guān)重要的是,它也可以像容易調(diào)用 C 代碼那樣被調(diào)用。當(dāng)你需要一些額外的一些其他功能時(shí),Rust 的無(wú)垃圾收集器和較的低運(yùn)行時(shí)需求,這兩點(diǎn)使得 Rust 成為一個(gè)嵌入到其他語(yǔ)言中的很好的方案。

在本教程中,我們有一整章來(lái)講述 FFI 和它的細(xì)節(jié),但是在本章中,我們將用三個(gè)例子來(lái)展示 FFI 的特定用例,它們分別是在 Ruby,Python 和 JavaScript 中。

問(wèn)題

這里我們有很多不同的項(xiàng)目可供選擇,但我們要選擇一個(gè)能展示 Rust 比其他許多語(yǔ)言有明顯優(yōu)勢(shì)的例子:數(shù)值計(jì)算和線程。

許多語(yǔ)言為了一致性,將數(shù)字存放在堆上,而不是在堆棧上。尤其是在專注于面向?qū)ο缶幊毯褪褂美占恼Z(yǔ)言上,默認(rèn)的分配模式是堆分配。有時(shí)候優(yōu)化會(huì)將特定的數(shù)字分配給堆棧,但它不是依靠?jī)?yōu)化器來(lái)完成的這項(xiàng)工作。同時(shí),我們可能希望確保我們使用的總是原始的數(shù)字類型而不是某種形式的對(duì)象類型。

第二問(wèn)題,許多語(yǔ)言有一個(gè)“全局解釋器鎖”,這在許多情況下限制了并發(fā)。這是以安全的名義來(lái)進(jìn)行的,本來(lái)這是一個(gè)好意,但是它限制了同一時(shí)間可以完成的工作量,這就非常不好了。

為了強(qiáng)調(diào)這兩個(gè)方面,我們要?jiǎng)?chuàng)建一個(gè)能使用到這兩方面的一個(gè)小項(xiàng)目。因?yàn)槭纠闹攸c(diǎn)是將 Rust 嵌入到其他語(yǔ)言,而不是這個(gè)問(wèn)題的本身,所以我們只用一個(gè)玩具例子:

    開(kāi)十個(gè)線程。每個(gè)線程內(nèi)部實(shí)現(xiàn)從一數(shù)到五百萬(wàn)。數(shù)完后,十個(gè)線程結(jié)束,并打印出“done!”。

這里我基于我計(jì)算機(jī)的能力選擇了五百萬(wàn)。下面是一個(gè)用 Ruby 寫的例子的代碼:

    threads = []

    10.times do
      threads << Thread.new do
        count = 0

        5_000_000.times do
          count += 1
        end
      end
    end

    threads.each {|t| t.join }
    puts "done!"

嘗試運(yùn)行這個(gè)例子,并選擇一個(gè)數(shù)字運(yùn)行幾秒鐘?;谀愕碾娔X硬件,你可能要增大或減小這個(gè)數(shù)字。

在我的系統(tǒng)中,運(yùn)行這個(gè)程序需要 2.156 秒。如果我用某種類似 top 的進(jìn)程監(jiān)控工具,我可以看到它在我的機(jī)器上只使用一個(gè) CPU 核。這是由于 GIL 在起作用。

雖然這確實(shí)是一個(gè)人工合成的程序,但是你也可以想象許多與現(xiàn)實(shí)世界相似的問(wèn)題。就我們的目的而言,運(yùn)行一些繁忙線程就代表了某種并行,昂貴的計(jì)算問(wèn)題。

Rust 庫(kù)

讓我們用 Rust 重寫這個(gè)問(wèn)題。首先,讓我們用 Cargo 創(chuàng)建一個(gè)新項(xiàng)目:

    $ cargo new embed
    $ cd embed

這個(gè)程序用 Rust 寫起來(lái)很簡(jiǎn)單:

    use std::thread;

    fn process() {
        let handles: Vec<_> = (0..10).map(|_| {
            thread::spawn(|| {
                let mut _x = 0;
                for _ in (0..5_000_001) {
                    _x += 1
                }
            })
        }).collect();

        for h in handles {
            h.join().ok().expect("Could not join a thread!");
        }
    }

這個(gè)程序中的一些內(nèi)容與從先前的例子看起來(lái)很相似。我們開(kāi)啟了十個(gè)線程,將它們收集成一個(gè) handles 向量。在每個(gè)線程中,我們都循環(huán)五百萬(wàn)次,每次給 _x 加一。這里為什么使用下劃線?嗯,如果我們刪除了它并編譯:

    $ cargo build
       Compiling embed v0.1.0 (file:///home/steve/src/embed)
    src/lib.rs:3:1: 16:2 warning: function is never used: `process`, #[warn(dead_code)] on by default
    src/lib.rs:3 fn process() {
    src/lib.rs:4     let handles: Vec<_> = (0..10).map(|_| {
    src/lib.rs:5         thread::spawn(|| {
    src/lib.rs:6             let mut x = 0;
    src/lib.rs:7             for _ in (0..5_000_001) {
    src/lib.rs:8                 x += 1
                 ...
    src/lib.rs:6:17: 6:22 warning: variable `x` is assigned to, but never used, #[warn(unused_variables)] on by default
    src/lib.rs:6             let mut x = 0;
                                 ^~~~~

第一個(gè)警告是因?yàn)槲覀冋跇?gòu)建一個(gè)庫(kù)。如果我們有一個(gè)關(guān)于此函數(shù)的測(cè)試,那么這個(gè)警告將會(huì)消失。但是現(xiàn)在,這個(gè)函數(shù)從未被調(diào)用過(guò)。

第二個(gè)警告與 x ,_x 有關(guān)。因?yàn)槲覀儗?duì) x 沒(méi)有做過(guò)任何操作,所以我們得到了一個(gè)警告。在我們的例子中,這是完全沒(méi)有問(wèn)題的,因?yàn)槲覀兙褪窍肜速M(fèi) CPU 周期。為 x 添加下劃線前綴就會(huì)消除這個(gè)警告。

最后,我們連接了每個(gè)線程。

然而現(xiàn)在這是一個(gè) Rust 庫(kù),它還沒(méi)有公開(kāi)任何從 C 中可調(diào)用的代碼。如果現(xiàn)在我們?cè)噲D將其鏈接到另一種語(yǔ)言,那么它是不能使用的。我們只需要做兩個(gè)小改變就能解決這個(gè)問(wèn)題。第一個(gè)就是修改我們代碼的開(kāi)始部分:

    #[no_mangle]
    pub extern fn process() {

我們必須添加一個(gè)新的屬性,no_mangle。當(dāng)你創(chuàng)建一個(gè) Rust 庫(kù)時(shí),在編譯輸出階段會(huì)改變函數(shù)的名稱。這個(gè)的原因超出了本教程的范圍。為了讓其他語(yǔ)言知道如何調(diào)用函數(shù),我們不需要改變函數(shù)的名稱。這個(gè)屬性就是將這個(gè)改變功能關(guān)掉。

另一個(gè)變化就是 pub extern。pub 意味著在這個(gè)模塊以外這個(gè)函數(shù)應(yīng)該是可調(diào)用的。extern 表示它應(yīng)該能夠從 C 中被調(diào)用。就這樣了!沒(méi)有很多變化了。

我們需要做的第二件事就是是改變 Cargo.toml 的設(shè)置。添加如下的內(nèi)容到底部:

    [lib]
    name = "embed"
    crate-type = ["dylib"]

這會(huì)告訴 Rust,我們想將我們的庫(kù)編譯成標(biāo)準(zhǔn)動(dòng)態(tài)庫(kù)。默認(rèn)情況下,Rust 會(huì)編譯成一個(gè) ‘rlib’,這是一個(gè) Rust 獨(dú)有的格式。

現(xiàn)在讓我們來(lái)構(gòu)建項(xiàng)目:

    $ cargo build --release
       Compiling embed v0.1.0 (file:///home/steve/src/embed)

我們選擇最優(yōu)化的構(gòu)建方式 cargo build --release。因?yàn)槲覀兿M绦蚰苓\(yùn)行的盡可能快!你可以從 target/release 中找到庫(kù)文件的輸出:

    $ ls target/release/
    build  deps  examples  libembed.so  native

這里的 libembed.so 是我們的‘共享對(duì)象’庫(kù)。我們可以像用 C 寫的對(duì)象庫(kù)一樣使用這個(gè)文件!說(shuō)句題外話,根據(jù)平臺(tái)的不同,這個(gè)文件可能是 embed.dll 或是 libembed.dylib。

現(xiàn)在我們已經(jīng)構(gòu)建了我們的 Rust 庫(kù),讓我們?cè)?Ruby 使用它吧。

Ruby

在我們的項(xiàng)目中打開(kāi) embed.rb 文件,并且按如下所做:

    require 'ffi'

    module Hello
      extend FFI::Library
      ffi_lib 'target/release/libembed.so'
      attach_function :process, [], :void
    end

    Hello.process

    puts "done!”

在我們可以運(yùn)行這個(gè)程序之前,我們要先安裝 ffi gem:

    $ gem install ffi # this may need sudo
    Fetching: ffi-1.9.8.gem (100%)
    Building native extensions.  This could take a while...
    Successfully installed ffi-1.9.8
    Parsing documentation for ffi-1.9.8
    Installing ri documentation for ffi-1.9.8
    Done installing documentation for ffi after 0 seconds
    1 gem installed

最終,我們可以試著運(yùn)行一下:

    $ ruby embed.rb
    done!
    $

哇,好快!在我的系統(tǒng)上,這花費(fèi)了 0.086 秒的時(shí)間,而不是純 Ruby 版本花費(fèi)的兩秒鐘。讓我們?cè)敿?xì)講一下這段 Ruby 代碼:

    require 'ffi

首先我們需要 ffi gem。這能讓我們像連接 C 庫(kù)一樣與 Rust 庫(kù)連接。

    module Hello
      extend FFI::Library
      ffi_lib 'target/release/libembed.so'

ffi gem 的作者推薦使用一個(gè)模塊來(lái)圈定我們將要從共享庫(kù)中導(dǎo)入的方法的作用域。在模塊里面,我們 extend 了必要的 FFI::Library 庫(kù)模塊,然后調(diào)用 ffi_lib 來(lái)加載共享對(duì)象庫(kù)。我們只是給它傳遞了我們庫(kù)文件的存儲(chǔ)路徑,正如我們之前看到的,這個(gè)路徑是 target/release/libembed.so。

    attach_function :process, [], :void

attach_function 方法是由 FFI gem 提供的。它是用來(lái)連接 Rust 與 Ruby 中同名函數(shù) process() 的。因?yàn)?process() 不需要任何參數(shù),所以第二個(gè)參數(shù)是一個(gè)空數(shù)組。因?yàn)樗环祷厝魏螙|西,所以我們傳遞 :void作為最后的一個(gè)參數(shù)。

    Hello.process

這才是對(duì) Rust 的實(shí)際調(diào)用。我們的 module 和 attach_function 的調(diào)用結(jié)合才成就了它。它看起來(lái)像是一個(gè) Ruby 函數(shù),但實(shí)際上是 Rust 的!

puts "done!"

最后,根據(jù)我們之前的項(xiàng)目的需求,我們打印出 done!

就是它了!正如我們所看到的,兩種語(yǔ)言之間的橋接是真的很容易,并且提升了很多性能。

接下來(lái),讓我們來(lái)試試 Python 吧!

Python

在目錄中創(chuàng)建一個(gè) embed.py 文件,并將如下內(nèi)容輸入:

    from ctypes import cdll

    lib = cdll.LoadLibrary("target/release/libembed.so")

    lib.process()

    print("done!")

這個(gè)更簡(jiǎn)單!我們用 ctypes 模塊中的 cdll。稍后對(duì) LoadLibrary 的一個(gè)快速調(diào)用后,我們就可以調(diào)用 process()了。

在我的系統(tǒng)中,這花費(fèi)了 0.017 秒??彀?!

Node.js

Node 不是一門語(yǔ)言,但它目前是服務(wù)器端 JavaScript 的主要實(shí)現(xiàn)。

為了用 Node 實(shí)現(xiàn) FFI,我們首先需要安裝庫(kù):

    $ npm install ffi

安裝完成后,我們就可以使用它了:

    var ffi = require('ffi');

    var lib = ffi.Library('target/release/libembed', {
      'process': [ 'void', []  ]
    });

    lib.process();

    console.log("done!");

與 Python 的例子相比,它看起來(lái)更像 Ruby 的例子。我們用 ffi 模塊獲得 ffi.Library(),它負(fù)責(zé)加載共享對(duì)象。我們要解釋下函數(shù)的返回類型和參數(shù)類型,返回的是‘空’,參數(shù)是一個(gè)空數(shù)組來(lái)表示的。從此之后,我們就可以調(diào)用它并打印結(jié)果。

在我的系統(tǒng)中,程序運(yùn)行花費(fèi)了 0.092 秒。

結(jié)論

正如你可以看到的,這些基本操作是非常簡(jiǎn)單的。當(dāng)然,這里我們還有很多可以做的。更多的細(xì)節(jié)請(qǐng)查看 FFI 章節(jié)。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)