2008年2月22日星期五

試驗SWIG (一) C++ 連接C#和Lua

今晚初次嘗試使用 SWIG (Simple Wrapper Interface Generator)。SWIG是一個能為C++程式生成各種Script語言Wrapper的工具。簡單地說,就是有了一個C++程式,用SWIG來連結這個程式和Script,使腳本語言可以呼叫C++的函數及類別等等。

SWIG支持大部份C++的功能,甚至template也能支持。同時可以生成十數種腳本語言的wrapper,包括我正在考慮使用的Lua、Python和C#等。

跟據我的目標,先做一個3D Vector的測試。以下是一部份C++代碼 (Vector3.h):

#pragma once
#include

struct Vector3 {
Vector3();
Vector3(const Vector3& v);
Vector3(float x, float y, float z);
~Vector3();

Vector3 operator+(const Vector3& v) const;
Vector3& operator=(const Vector3& v);
Vector3& operator+=(const Vector3& v);
Vector3& operator-=(const Vector3& v);
Vector3& operator*=(float rhs);
Vector3& operator/=(float rhs);

bool operator==(const Vector3& v) const;
bool operator!=(const Vector3& v) const;

float GetSquaredLength() const;
float GetLength() const;
Vector3 GetNormalized() const;
void Normalize();

float x, y, z;
};

inline Vector3::Vector3(const Vector3& v) : x(v.x), y(v.y), z(v.z) {
}

inline Vector3& Vector3::operator+=(const Vector3& v) {
x += v.x;
y += v.y;
z += v.z;
return *this;
}

inline Vector3& Vector3::operator*=(float rhs) {
x *= rhs;
y *= rhs;
z *= rhs;
return *this;
}
// ...


之後寫一個SWIG的interface檔案 (Math.i):

%module Math
%{
#include "Vector3.h"
%}

%rename(Add) Vector3::operator+;
%rename(AddEqual) Vector3::operator+=;
%rename(MultiplyEqual) Vector3::operator*=;
// ...

%include "Vector3.h"


最後幾行是因為,以我所知,SWIG對C#並不支持operator overloading。SWIG本身是支持operator overloading的,或許要深入研究一下SWIG的Wrapper/Proxy生成過程可不可以加入這功能。

C#的測試

做C#的時候,用swig -c++ -csharp -o Math_wrap.cxx Math.i,就會新成三個檔案,分別為用來做C++ Wrapper的Math_wrap.cxx、用來做C# Proxy的MathPInvoke.cs和Vector3.cs。

接著就把Math_wrap.cxx編成DLL,而MathPInvoke.cs和Vector3.cs就可以放到C#的Project裡使用。

剛看到GDC的XNA演講提及.Net的速度,就直接用來比較一下。使用了SWIG生成的Proxy,使用Vector3類別和C#寫的沒有分別:

static void Main(string[] args)
{
Vector3 Position = new Vector3(0, 0, 0);
Vector3 Velocity = new Vector3(10000, 10000, 10000);
const float Friction = 0.9f;

const int count = 10000000;
System.DateTime start = System.DateTime.Now;

for (int i = 0; i < count; i++)
{
Position.AddEqual(Velocity);
Velocity.MultiplyEqual(Friction);
}

System.DateTime end = System.DateTime.Now;
System.Console.WriteLine((double)count / (end - start).TotalSeconds);
}


結果留到最後和Lua一起比較吧。

Lua的測試

之前的Math.i和其他檔案都不用改,直接執行swig -c++ -lua -o Math_wrap.cxx math.i,就會生成Math_wrap.cxx這個proxy。和C#不同,是不需要Lua的Proxy的。把這個連結成Math.dll後,在Lua只需一句Require("Math")就行了!這實在太簡單!(這麼簡單的功能是在Lua 5.1才有)

同樣的測試用Lua來寫:

require("Math")

local Position = Math.Vector3(0, 0, 0)
local Velocity = Math.Vector3(10000, 10000, 10000)
local Friction = 0.9

local count = 1000000

local start = os.clock()

for i = 1, count do
Position:AddEqual(Velocity)
Velocity:MultiplyEqual(Friction)
end

local finish = os.clock()
print(count / (finish - start))


以前看了Lua的書,但沒有寫過,發覺Lua寫起來也很簡單,而且SWIG做的Binding也不錯。最容易錯的地方就是把object:method(...)寫成object.method(...)。

結果和分析

結果是以每秒執行iteration次數計算,數字越大表示越好。






Caseiteration/secrelative performance
C++1,544,163x
C#519,3720.336x
Lua357,6530.232x
XNA on Xbox3603,380,0002.19x
這個比較是有數個問題。一個估計XNA的測試裡會有大量的Particle instances,但我的測試只用了一組local variables,多instance會導致cache miss,應該會慢許多,另外這亦需要用iterator或index去存取instances。另外是我的電腦是Core2 2.4G,應該比XBox360快。再者,XNA應該有使用SIMD指令做優化。

不論這個結果準確與否,在對比C++的結果似乎這個最簡單使用SWIG的Wrapper在速度上還存有改善空間。而C#比Lua快應該是基於它使用JIT,而Lua只是一個interpreter。但就這個例子而言,使用Script的速度能達到直接使用C++的二至三成,已經原全超出我的預期。

接下來我想試一試:
  • 使用C++/CLI做Binding代替SWIG/PInvoke,看看速度的差異。如果能把Vector3當成value type,速度應該會快reference type許多,建立物件的overhead亦會減低 (不需要GC)。如果成功,可以把一部份常用類別用C++/CLI人手編寫,其他就用SWIG。
  • 嘗試寫SIMD優化幾個operators,和XNA的結果比較。
  • 測試SWIG對類別、smart pointer、STL等。

2 則留言:

Ricky 說...

Lua 也有 Jit 的.
不過我認為 Jit 再強, 如果一個 language 沒有 low level construct 如 custom memory management 最終也不會快過 C/C++.
這裡有一篇文章正正道出為何 Java 老是比 C/C++ 低效.

不過 C#/Lua 的性能應當能勝任你的用途. 始終你注意得到的地方, 就不會是問題.

Milo 說...

真的不知道 Lua 都有 JIT。

你要找一個腳本語言比 C++ 快,都幾高難度啊(^_^)。不過我覺得 Lua/Java 比 C# 慢的一個原因在於 C# 有 value type。可以把 value type 放在 stack 而不用 GC 。如果在腳本中使用大量 vector/matrix 等運算,效能上可能會是個大差別。

如你所說,我目前只是去了解它們的優點和缺點,並注意可能出現的問題,就可以比較得心應手了。