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次數計算,數字越大表示越好。
Case | iteration/sec | relative performance |
C++ | 1,544,163 | x |
C# | 519,372 | 0.336x |
Lua | 357,653 | 0.232x |
XNA on Xbox360 | 3,380,000 | 2.19x |
不論這個結果準確與否,在對比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 則留言:
Lua 也有 Jit 的.
不過我認為 Jit 再強, 如果一個 language 沒有 low level construct 如 custom memory management 最終也不會快過 C/C++.
這裡有一篇文章正正道出為何 Java 老是比 C/C++ 低效.
不過 C#/Lua 的性能應當能勝任你的用途. 始終你注意得到的地方, 就不會是問題.
真的不知道 Lua 都有 JIT。
你要找一個腳本語言比 C++ 快,都幾高難度啊(^_^)。不過我覺得 Lua/Java 比 C# 慢的一個原因在於 C# 有 value type。可以把 value type 放在 stack 而不用 GC 。如果在腳本中使用大量 vector/matrix 等運算,效能上可能會是個大差別。
如你所說,我目前只是去了解它們的優點和缺點,並注意可能出現的問題,就可以比較得心應手了。
發佈留言