Multithreaded Libraries Performance
The single-threaded CRT is no longer ( in vs2005 ) available.This topic discusses how to get the maximum performance from themultithreaded libraries.
The performance of the multithreaded libraries has been improvedand is close to the performance of the now-eliminatedsingle-threaded libraries. For those situations when even higherperformance is required, there are several new features.
· Independent stream locking allows you to lock a stream andthen use _nolockFunctions that access the stream directly. This allows lockusage to be hoisted outside critical loops.
· Per-thread locale reduces the cost of locale access formultithreaded scenarios (see _configthreadlocale).
· Locale-dependent functions (with names ending in _l) take thelocale as a parameter, removing substantial cost (for example,printf, _printf_l, wprintf, _wprintf_l).
· Optimizations for common codepages reduce the cost of manyshort operations.
· Defining _CRT_DISABLE_PERFCRIT_LOCKSforces all I/O operations to assume a single-threaded I/O model anduse the _nolock forms of the functions. This allows highlyI/O-based single-threaded applications to get betterperformance.
· Exposure of the CRT heap handle allows you to enable theWindows Low Fragmentation Heap (LFH) for the CRT heap, which cansubstantially improve performance in highly scaled scenarios.
运行时库是程序在运行时所需要的库文件 ,通常运行时库是以 LIB 或 DLL 形式提供的。 C运行时库诞生于 20 世纪 70年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于新观念。所以这个时期的 C运行时库都是单线程的。
随着操作系统多线程技术的发展,最初的 C 运行时库无法满足程序的需求,出现了严重的问题。 C 运行时库使用了多个全局变量(例如errno )和静态变量,这可能在多线程程序中引起冲突。假设两个线程都同时设置 errno ,其结果是后设置的 errno会将先前的覆盖,用户得不到正确的错误信息。
因此, Visual C++ 提供了两种版本的 C运行时库。一个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库有两个重大差别:
( 1 )类似 errno 的全局变量,每个线程单独设置一个。这样从每个线程中可以获取正确的错误信息。
( 2 )多线程库中的数据结构以同步机制加以保护。这样可以避免访问时候的冲突。
Visual C++ 提供的多线程运行时库又分为静态链接库和动态链接库两类,而每一类运行时库又可再分为 debug 版和release 版,因此 Visual C++ 共提供了 6 个运行时库。如下表:
C 运行时库 | 库文件 |
Single thread(static link) ML | libc.lib |
Debug single thread(static link) MLd | libcd.lib |
MultiThread(static link) MT | libcmt.lib |
Debug multiThread(static link) MTd | libcmtd.lib |
MultiThread(dynamic link) MD | msvert.lib |
Debug multiThread(dynamic link) MDd | msvertd.lib |
2.C 运行时库的作用
C 运行时库除了给我们提供必要的库函数调用(如 memcpy 、 printf 、 malloc等)之外,它提供的另一个最重要的功能是为应用程序添加启动函数。
C 运行时库启动函数的主要功能为进行程序的初始化,对全局变量进行赋初值,加载用户程序的入口函数。
不采用宽字符集的控制台程序的入口点为 mainCRTStartup(void)。下面我们以该函数为例来分析运行时库究竟为我们添加了怎样的入口程序。这个函数在 crt0.c中被定义,下列的代码经过了笔者的整理和简化:
void mainCRTStartup(void)
{
int mainret;
_osver = GetVersion();
_winminor = (_osver >> 8)& 0x00FF ;
_winmajor = _osver & 0x00FF ;
_winver = (_winmajor << 8) +_winminor;
_osver = (_osver >> 16)& 0x00FFFF ;
_ioinit();
_acmdln = (char *) GetCommandLineA();
_aenvptr = (char *) __crtGetEnvironmentStringsA();
_setargv();
_setenvp();
_cinit();
__initenv = _environ;
mainret = main( __argc, __argv, _environ );
exit( mainret );
}
从以上代码可知,运行库在调用用户程序的 main 或 WinMain函数之前,进行了一些初始化工作。初始化完成后,接着才调用了我们编写的 main 或 WinMain 函数。只有这样,我们的 C语言运行时库和应用程序才能正常地工作起来。
除了 crt0.c 外, C 运行时库中还包含 wcrt0.c 、 wincrt0.c 、 wwincrt0.c三个文件用来提供初始化函数。 wcrt0.c 是 crt0.c 的宽字符集版, wincrt0.c 中包含 windows应用程序的入口函数,而 wwincrt0.c 则是 wincrt0.c 的宽字符集版。
Visual C++ 的运行时库源代码缺省情况下不被安装。如果您想查看其源代码,则需要重装 Visual C++,并在重装在时选中安装运行库源代码选项。
3. 各种 C运行时库的区别
( 1 )静态链接的单线程库
静态链接的单线程库只能用于单线程的应用程序, C运行时库的目标代码最终被编译在应用程序的二进制文件中。通过 /ML 编译选项可以设置 Visual C++使用静态链接的单线程库。
( 2 )静态链接的多线程库
静态链接的多线程库的目标代码也最终被编译在应用程序的二进制文件中,但是它可以在多线程程序中使用。通过 /MT 编译选项可以设置Visual C++ 使用静态链接的单线程库。
( 3 )动态链接的运行时库
动态链接的运行时库将所有的 C 库函数保存在一个单独的动态链接库 MSVCRTxx.DLL 中, MSVCRTxx.DLL处理了多线程问题。使用 /MD 编译选项可以设置 Visual C++ 使用动态链接的运行时库。
/MDd 、 /MLd 或 /MTd 选项使用 Debug runtime library( 调试版本的运行时刻函数库 ) ,与/MD 、 /ML 或 /MT 分别对应。 Debug 版本的 Runtime Library包含了调试信息,并采用了一些保护机制以帮助发现错误,加强了对错误的检测,因此在运行性能方面比不上 Release 版本。
下面看一个未正确使用 C 运行时库的控制台程序 :
#include <stdio.h>
#include <afx.h>
int main()
{
CFile file;
CString str("I love you");
TRY
{
file.Open("file.dat",CFile::modeWrite |CFile::modeCreate);
}
CATCH( CFileException, e )
{
#ifdef _DEBUG
afxDump << "File could not beopened " << e->m_cause<< "n";
#endif
}
END_CATCH
file.Write(str,str.GetLength());
file.Close();
}
我们在 "rebuild all" 的时候发生了 link 错误:
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved externalsymbol __endthreadex
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved externalsymbol __beginthreadex
main.exe : fatal error LNK1120: 2 unresolved externals
Error executing cl.exe.
发生错误的原因在于 Visual C++ 对控制台程序默认使用单线程的静态链接库,而 MFC 中的 CFile类已暗藏了多线程。我们只需要在 Visual C++6.0 中依次点选Project->Settings->C/C++ 菜单和选项,在Project Options 里修改编译选项即可。
不过最上面的那个程序在6.0里面是可以运行的,现象同2003的是一样的。
***********************************************
从字面上看,运行库是程序在运行时所需要的库文件。通常运行库是以 DLL 形式提供的。 Delphi 和C++ Builder 的运行库为 .bpl 文件,实际还是一个 DLL。运行库中一般包括编程时常用的函数,如字符串操作、文件操作、界面等内容。不同的语言所支持的函数通常是不同的,所以使用的库也是完全不同的,这就是为什么有VB 运行库、 C运行库、 Delphi 运行库之分的原因。即使都是 C++ 语言,也可能因为提供的函数不同,而使用不同的库。如 VC++ 使用的运行库和 C++Builder 就完全不同。
如果不使用运行库,每个程序中都会包括很多重复的代码,而使用运行库,可以大大缩小编译后的程序的大小。但另一方面,由于使用了运行库,所以在分发程序时就必须带有这些库,比较麻烦。如果在操作系统中找不到相应的运行库程序就无法运行。为了解决这个矛盾,Windows 总是会带上它自己开发的软件的最新的运行库。象Windows 2000 以后的版本都包括 Visual Basic 5.0/6.0 的库。 Internet Explorer 总是带有最新的 Visual C++ 6.0 的库。 WindowsXP 带有 Microsoft .NET 1.0 (用于VB.NET 和 C#)的库。 Visual C++ 、 Delphi 和 C++ Builder允许用户选择所编译得到的程序是否依赖于运行库。而 VB 、FoxPro 、 PowerBuilder 、 LabWindows/CVI 和 Matlab 就不允许用户进行这种选择,必须依赖于运行库。
小结
看了上面这么多咚咚以后(我估计没几个人会有这个耐心把这么多东西看完的,娃哈哈),不过我还是把它完整地看完了,中间那一段是抄的,讲得很好,讲得非常清楚。嗯。有一点是可以肯定的,那就是不要用ML 单线程版本,况且 2005 已经不支持 ML (注意,这里ML 不是 MakeL*ve 的缩写,汗!)了。另外, ML不支持多线程的,所以如果使用 ML来编译运行的话,肯定会出很多问题的,虽然它没有明确说出会发生什么样的问题。
一个困扰偶很长时间的问题终于解决。把 MLd 改为 MDd 所有问题就都解决了,用Intel Thread Checker check了一下也没问题。如果大家有碰到同样的问题的话,希望以上能够给你一点有用的信息。有啥问题,欢迎与我联系。有啥说的不对的,请批评指正。恩。