C#目前是微软.NET平台首推的应用程序开发语言。C#编写的应用程序必须运行在一个特殊的环境中,即受控环境(managed)。与以往非受控(unmanaged)的C++相比,C#应用程序的性能到底如何呢?经过几个方面的性能测试,我们发现C#应用程序的运行速度远远不如非受控C++应用程序。在这场速度的大比拼中,非受控C++具有明显的优势。它将一如既往地成为大多数程序员的最爱。
本文拟通过一些正统的和非正统的性能测试方法,对C#和C++程序进行测试。在这些测试的统计数据中。读者会发现C#程序的性能到底如何。做为程序员,我对C++情有独钟。在我的职业生涯中,一直都在使用Visual C++。希望本文能给程序员们一点提示,以便在编写不同的应用时选择适合的编程语言。
Visual C++是Win32平台上性能最好的编译器之一,我想这已经成为一种共识。.NET是微软提供的一种新的应用平台。
首先我说明一下自己机器的软硬件环境:
硬件环境:Dell Inspiron 3800 G700GT 笔记本电脑 CPU/PIII/700,ROM/128MB,HD/12GB。
软件环境:Windows 2000 + SP2,.NET平台 + Visual Studio.NET,Office XP。
所有的测试均是在命令行状态下以RELEASE模式编译程序,而非Visual Studio IDE集成开发环境,并在命令行状态下执行程序。没有对编译过程进行任何优化。
本文的测试由四个部分组成。其中包括用著名的埃拉托色尼过滤算法(Sieves of Eratosthenes)进行的测试及其它的单项测试,单项测试主要的考察.NET框架中特定项目的性能:
Hello World 测试
.NET框架的一个问题是程序的启动时间。因为.NET框架运行在Win32之上,因此要启动一个.NET框架程序需要额外的启动时间开销。
埃拉托色尼过滤算法(Sieve of Eratosthenes)测试
这个过滤算法是一个古老的寻找素数的方法。因为开发这个算法是为人类所用,因此这个算法很占用CPU资源(非CPU优化),从而能提供非常好的基准反映人为因素。记住,编写程序的总是人。
数据库存取测试
当今应用程序服务器往往都要用到数据库,所以我觉得用ADO.NET来测试C#的数据库存取性能与用常规ADO测试Visual C++的数据库存取性能进行比较是很能说明问题的一个好方法。
XML测试
XML是一种最新的并且是流行的技术。因此许多人都会对用C# 和Visual C++解析XML的性能感兴趣。
本文不打算对测试结果进行详细解释。在每一项测试中,我首先将用于测试的算法和代码列出来。然后再列出测试结果数据。最后,针对这些数据勾勒出简要的结论。
有一点在测试过程中相当重要,那就是我试图使两个环境的测试代码尽可能相同。这样可以使得测试结果更有说服力。
Hello World
Hello World测试程序主要是评测加载一个程序及其运行时环境所用的时间。C++的程序运行需要C运行库,从所周知,这个库是相当轻量级的。而C#程序的运行必须要加载.NET框架,从目前的情况看,这个框架无庸置疑不是一个轻量级的。
Hello World程序的C++代码如下: 代码一:helloworld.cpp
#include
int main(int argc, char *argv[])
{
std::cout << "Hello World" << std::endl;
return 0;
};
Hello World程序的C#代码如下: 代码二:helloworld2.cs
using System;
namespace HelloWorld
{
class Class1
{
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
}
从这个测试的结果中,我们可以看到程序在相应环境中的加载时间。对于一个只完成简单任务的程序来说,无疑我们需要它能快速加载和退出。Perl脚本是个有代表性的例子,运行这种需要大量的加载时间,从而导致其不能满足基于CGI且面向性能的Web站点的需要。这时,人们常常选择C++程序替Perl脚本。但对于需要长时间处于激活状态的程序,其加载时间相对运行时性能来说就显得并不是那么很重要了。下表是十次测试的结果:
表一:Hello World 测试结果 序号 C++(~毫秒) C#(~毫秒)
1 40 1221
2 20 121
3 10 130
4 10 100
5 10 110
6 10 130
7 10 120
8 10 140
9 10 150
10 20 140
平均值 15 235
测试结果的精确程度由GetTickCount函数的精度决定。其精度大概在百分之一秒。从结果我们可以得出这样的结论。第一,冷启动.NET应用过程所花费的时间比运行相同的应用多出一秒的时间。第二,启动后的程序再运行时,在C++代码大约是C#代码运行时间开销的十分之一。一般这种差别可以忽略不计。
埃拉托色尼过滤算法测试
埃拉托色尼过滤算法测试程序评估基本的整型算法和比较逻辑。这个算法历史悠久,它早在计算机出现之前就已经存在。因此用它来评估人们在各种环境中创建的算法性能具有一定的典型性或代表性。
埃拉托色尼过滤算法的C++代码如下:
代码三:sieve.cpp
#include
#include
#include
#include
#include
using namespace std;
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr << "Usage:\tsieve [iterations]\n";
return 1;
};
size_t NUM = atoi(argv[1]);
DWORD dw = ::GetTickCount();
vector primes(8192 + 1);
vector::iterator pbegin = primes.begin();
vector::iterator begin = pbegin + 2;
vector::iterator end = primes.end();
while (NUM--)
{
fill(begin, end, 1);
for (vector::iterator i = begin;
i < end; ++i)
{
if (*i)
{
const size_t p = i - pbegin;
for (vector::iterator k = i + p;
k < end; k += p)
{
*k = 0;
}
}
}
}
DWORD dw2 = ::GetTickCount();
std::cout << "Milliseconds = " << dw2-dw
<< std::endl;
return 0;
}
下面是埃拉托色尼过滤算法的C#代码: 代码四:
using System;
namespace Sieve
{
class Class1
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage:\tsieve "
"[iterations]");
return;
}
int NUM = int.Parse(args[0]);
long dt = DateTime.Now.Ticks;
int[] primes = new int[8192+1];
int pbegin = 0;
int begin = 2;
int end = 8193;
while (NUM-- != 0)
{
for (int i = 0; i < end; i++)
{
primes[i] = 1;
}
for (int i = begin; i < end; ++i)
{
if (primes[i] != 0)
{
int p = i - pbegin;
for (int k = i + p; k < end; k += p)
{
primes[k] = 0;
};
}
};
};
long dt2 = DateTime.Now.Ticks;
System.Console.WriteLine("Milliseconds = {0}",
(dt2-dt)/10000);
}
}
}
测试的结果并不足以说明那一种环境更快。在这两个语言的测试中,我旨在说明哪一种语言的构造对测试的结果影响最大。当你基于性能的考虑来选择某种语言时,应该直接考虑心需要哪种类型的性能。在这里,埃拉托色尼过滤算法测试的是循环构造以及比较逻辑和整数基本类型的处理。下面是十次测试,每次进行10000次重复的测试结果:
表二:过滤算法测试结果 序号 C++(~毫秒) C#(~毫秒)
1 1342 2724
2 1342 2714
3 1342 2724
4 1342 2724
5 1342 2734
6 1342 2724
7 1362 2734
8 1352 2734
9 1362 2724
10 1352 2724
平均值 1348 2726
这个结果很能说明问题。整数计算C#所花的时间是C++的两倍。所以对于一个逻辑复杂的服务器来说,使用非受管C++代码比C#代码更适合。
上面的C++代码和C#代码之间有一个差别,即C#使用的是本机数组,而C++代码用的是向量模板类,我用本机数组重写了C++代码,按说应该更快。是结果不是这样,本机C++数组执行用时是1900毫秒。
转者注:
C#数据库速度较快不能说明C#本身性能好,因为他们通过外部调用实现,时间集中在外部调用上,就如同
for(int i=0;i<100;i++)
{
//外部调用1秒钟
}
C#用125秒,C++用100秒,怎么能用慢了25%来衡量?C#速度的测试结果和Java相当,实际上Java和C#都比C++慢了5~20倍(要考虑处理不同的数据性能不同,比如整型、浮点型、字符串,要考虑在堆中分配内存造成的速度降低,要考虑后台释放内存的开销,还要考虑其他因素,比如Java/C#中字符串相加和C++中strcpy的巨大效率差异)。
另外,开发效率也是首先需要考虑的,这方面,我认为c#比c++好。
至于,你选择哪种开发工具并不是本文要讨论的,具体应用不同,平台不同,开发工具自然不同。
数据库存取测试
在这一部分,我们将用C++和C#代码来测试两者对数据库的存取和处理。方法是对同一个数据库表进行操作。表结构如下: CREATE TABLE testtable
(
col1 INTEGER,
col2 VARCHAR(50),
PRIMARY KEY (col1)
)
测试将分三个部分,第一部分和第三部分集中对数据处理,第二部分集中对数据存取。数据处理和数据存取的测试结果是分开呈现的。 下面是数据存取和处理的C++代码: 代码五:db.cpp
#import "msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include <iostream>
#include <string>
#include <sstream>
int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage:\tdb [rows]\n";
return 1;
};
::CoInitialize(NULL);
int NUM = atoi(argv[1]);
DWORD dw = ::GetTickCount();
_ConnectionPtr conptr(__uuidof(Connection));
conptr->Open(L"Provider=Microsoft.Jet.OLEDB.4.0;"
"Data Source=c:\\db.mdb;",
L"",
L"",
adOpenUnspecified);
for (int i=0;i<NUM;i++)
{
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
std::wstringstream ss;
ss << L"INSERT INTO testtable (col1, col2) "
<< "VALUES ("
<< i+1 << L", ''''" << i+1 << L"'''')";
_bstr_t sql = ss.str().c_str();
conptr->Execute(sql, &RecordsEffected, adCmdText);
};
DWORD dw2 = ::GetTickCount();
std::cout << "Milliseconds = " << dw2-dw
<< std::endl;
dw = ::GetTickCount();
for (int j=0;j<100;j++)
{
_RecordsetPtr rsptr(__uuidof(Recordset));
rsptr->Open(L"SELECT col1, col2 FROM testtable",
conptr.GetInterfacePtr(),
adOpenForwardOnly, adLockOptimistic, adCmdText);
while (rsptr->EndOfFile)
{
_variant_t v1 = rsptr->GetCollect("col1");
_variant_t v2 = rsptr->GetCollect("col2");
rsptr->MoveNext();
};
rsptr->Close();
};
dw2 = ::GetTickCount();
std::cout << "Milliseconds = " << dw2-dw
<< std::endl;
dw = ::GetTickCount();
for (int i=0;i<NUM;i++)
{
std::wstringstream ss;
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
ss << L"DELETE FROM testtable WHERE col1 = "
<< i+1;
_bstr_t sql = ss.str().c_str();
conptr->Execute(sql, &RecordsEffected, adCmdText);
};
conptr->Close();
dw2 = ::GetTickCount();
std::cout << "Milliseconds = " << dw2-dw
<< std::endl;
::CoUninitialize();
return 0;
}
下面是数据存取和处理的C#代码: 代码六:db2.cs
using System;
using System.Data;
using System.Data.OleDb;
using System.Text;
namespace Db
{
class Class1
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage:\tdb2 [rows]");
return;
}
int NUM = int.Parse(args[0]);
long dt = DateTime.Now.Ticks;
OleDbConnection connection;
connection = new OleDbConnection(
"Provider=Microsoft.Jet.OLEDB.4.0;"
"Data Source=c:\\db.mdb;");
connection.Open();
for (int i=0;i<NUM;i++)
{
StringBuilder ss = new StringBuilder();
ss.Append("INSERT INTO testtable (col1, col2)"
" VALUES (");
ss.Append(i+1);
ss.Append(", ''''");
ss.Append(i+1);
ss.Append("'''')");
OleDbCommand command = new OleDbCommand(
ss.ToString(), connection);
command.ExecuteNonQuery();
};
long dt2 = DateTime.Now.Ticks;
System.Console.WriteLine("Milliseconds = {0}",
(dt2-dt)/10000);
dt = DateTime.Now.Ticks;
for (int j=0;j<100;j++)
{
DataSet dataset = new DataSet();
OleDbCommand command = new OleDbCommand(
"SELECT col1, col2 FROM testtable",
connection);
OleDbDataReader reader =
command.ExecuteReader();
while (reader.Read() == true)
{
int v1 = reader.GetInt32(0);
string v2 = reader.GetString(1);
};
reader.Close();
};
dt2 = DateTime.Now.Ticks;
System.Console.WriteLine("Milliseconds = {0}",
(dt2-dt)/10000);
dt = DateTime.Now.Ticks;
for (int i=0;i<NUM;i++)
{
StringBuilder ss = new StringBuilder();
ss.Append("DELETE FROM testtable "
"WHERE col1 = ");
ss.Append(i+1);
OleDbCommand command = new OleDbCommand(
ss.ToString(), connection);
command.ExecuteNonQuery();
};
connection.Close();
dt2 = DateTime.Now.Ticks;
System.Console.WriteLine("Milliseconds = {0}",
(dt2-dt)/10000);
}
}
}
下表为运行十次,每次100行记录的结果 表三:数据库测试结果
序号
C++(~毫秒)
C#(~毫秒)
1 1612/441/450 4086/630/560
2 391/410/441 490/630/520
3 370/421/440 480/510/440
4 371/420/451 470/510/450
5 370/421/461 460/500/450
6 371/420/461 470/500/460
7 370/411/471 470/500/460
8 381/410/451 460/510/470
9 370/421/450 470/510/470
10 391/410/461 460/510/470
平均值 499/419/454 832/531/475
这个结果让人十分惊讶。.NET在这里的表现是令人满意的。一般来说下降百分之二十五的性能是可以忍受的。这说明.NET在这里是赢家。
XML性能测试
XML是数据处理领域的最新技术。许多人对用C#代码和Visaul C++代码处理或解析XML文件的性能很感兴趣。
下面是一段存取和处理XML的C++代码:
代码七:xml.cpp
#import &tl;msxml3.dll≷ named_guids
#include &tl;iostream≷
int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cerr &tl;&tl; "Usage:\txml [filename]\n";
return 1;
};
::CoInitialize(NULL);
DWORD dw = ::GetTickCount();
for (int i=0;i&tl;100;i++)
{
MSXML2::IXMLDOMDocumentPtr DomDocument(
MSXML2::CLSID_DOMDocument) ;
_bstr_t filename = argv[1];
DomDocument-≷async = false;
DomDocument-≷load(filename);
}
DWORD dw2 = ::GetTickCount();
std::cout &tl;&tl; "Milliseconds = " &tl;&tl; dw2-dw
&tl;&tl; std::endl;
::CoUninitialize();
return 0;
}
下面是一段存取和处理XML的C#代码: 代码七:xml.cs
using System;
using System.Xml;
namespace xml2
{
class Class1
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage:\txml [filename]");
return;
}
long dt = DateTime.Now.Ticks;
for (int i=0;i&tl;100;i++)
{
XmlDocument doc = new XmlDocument();
doc.Load(args[0]);
}
long dt2 = DateTime.Now.Ticks;
System.Console.WriteLine("Milliseconds = {0}", (dt2-dt)/10000);
}
}
}
运行十次的结果如下:
表四:XML 测试结果
序号 C++(~毫秒) C#(~毫秒)
1 241 1111
2 170 841
3 161 841
4 170 861
5 160 861
6 171 851
7 170 841
8 160 831
9 160 841
10 170 851
平均值 203 873
这个结果又让人惊讶一次。很难相信.NET XML类的运行效率比同等的ActiveX类要慢四至五倍。为什么会发生这种情况呢?也许只有微软的兄弟才知道。也许微软想要把.NET类设计成在某一方面与众不同。如果不是这样,那么微软的那帮家伙应该好好优化一下他们的.NET XML类。
如果你是一个Web 服务和服务器应用的开发人员,当把.NET框架用于需要很高性能的应用时,尤其是XML服务时,应三思而行。
总结
需要强调的一点是.NET框架还是一种新的技术。因此,在这个框架中需要做的事情还有很多,它还需要不断优化。另外,这里对.NET的性能测试也很肤浅,以.NET所拥有的丰富内涵来说,用以上四个方面的测试以及简陋的文章来说明它的快或者慢是远远不够的。(全文完)
回答
我来到这个世界上,
只带着纸、绳索和身影,
为了在审判前,
宣读那些被判决的声音。
告诉你吧,世界
我--不--相--信!
纵使你脚下有一千名挑战者,
那就把我算作第一千零一名。
我不相信天是蓝的,
我不相信雷的回声,
我不相信梦是假的,
我不相信死无报应。
如果海洋注定要决堤,
就让所有的苦水都注入我心中,
如果陆地注定要上升,
就让人类重新选择生存的峰顶。
新的转机和闪闪星斗,
正在缀满没有遮拦的天空。
那是五千年的象形文字,
那是未来人们凝视的眼睛。