65.9K
CodeProject 正在变化。 阅读更多。
Home

中小型办公室的分布式计算

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (64投票s)

2008年8月23日

CPOL

21分钟阅读

viewsIcon

143690

分布式计算开源库hxGrid简介。该库的主要优点:集群仅利用Windows 2000/XP/Vista工作站的空闲时间(无需专用工作站);易于使用;免费。

引言



游戏开发工具编程中,有些任务需要大量的计算能力。例如,在我们的关卡编辑器中计算游戏关卡光照需要长达24小时。显然,操作时间越长,使用频率越低,我们将不得不满足于质量较低的结果,因为良好的调整需要大量的迭代。

最近,我非常关注GPGPU——利用图形处理单元(GPU)的强大功能来加速我们的工具。我们关卡编辑器的早期版本通过从光照贴图的每个纹素渲染半立方体侧视图来执行光照计算(环境光遮蔽[2] + 直接光照)。不幸的是,实验表明,由于视频内存到CPU传输速率慢,一个简单的纯CPU光线追踪器可以快2-3倍地完成此任务。如果整个算法在GPU上实现,我可能会获得更好的结果,但这将需要大量工作,因为GPGPU编程模型很复杂。直到最近,nVidia才提供了一个相对简单的C框架用于GPGPU编程[3][28]。多核CPU在该领域显示出良好的前景。与GPU不同,第二个核心可以执行相同的二进制代码。无需为不同的编程模型和语言调整算法即可利用额外的性能。

在我开始为多核CPU优化工具之前,我就明白我需要更广泛地思考。:)

image2.gif

在我们继续之前,我请读者按下CTRL+ALT+DEL,运行任务管理器并切换到“性能”选项卡。看看“CPU使用率”图。我敢打赌大多数读者会看到低于10%的值。在双核CPU上,第二个核心大部分时间都处于空闲状态。

在典型工作中,计算机的全部功率仅在短时间内爆发式使用,以响应用户的操作。当用户在Microsoft WordTM中输入文本时,从处理器的角度来看,键盘事件之间的暂停是永恒的。事实上,充分利用可用处理能力的应用程序数量并不高。

现在计算一下一个中型办公室(50多个工作站)可用的计算能力,你将得到一个与现代超级计算机相当的值。

image4.jpg

不幸的是,集群计算与高科学的传统关联对我来说是一个心理障碍,使我不敢尝试在日常任务中使用分布式计算。幸运的是,一个出色的应用程序 IncredyBuild[4] 表明,分布式计算不仅可以用于日常任务,甚至可能用于实时任务。

数据并行编程模型

通过TCP/IP网络连接的多个工作站可以被视为具有分布式内存架构的超级计算机。这些系统最简单的模式是数据并行计算。

image3.gif

换句话说,如果某个普通应用程序处理一个独立的元素数组,那么在数据并行模型中,每个处理器被分配处理数组的一部分。为了支持数据并行计算,核心库应该将任务分成多个部分,将任务数据传输到特定CPU的本地内存,在该CPU上运行任务,将结果传回给调用者,并提供从调用者请求一些全局数据的能力。

这项工作由集群支持软件完成。我搜索了可用的软件,并考虑了以下要求:

  • 工作站运行Windows XP;
  • 集群只允许使用空闲时间。工作站用户不应遇到任何速度变慢;
  • 高容错性——集群不是专用的。任何工作站都可以重新启动、离线或被用户占用,但这不应影响整个集群的稳定性和状态。

第一个要求排除了大多数候选者[5][6]。原因很清楚——如果免费的Linux可用,为什么有人会为每个集群工作站购买商业操作系统呢?

第二个和第三个要求排除了所有其他候选者,尽管我想提及一些最有前途的库。

image7.jpg

MPICH2[7]。这是MPI库的Windows实现。不幸的是,它以正常优先级运行隐藏进程,这会影响工作站用户。如果工作站脱机,MPI会话将以错误终止。

image5.jpg

libGlass[8]。尽管编程模型简单,但我将此候选者搁置,因为该库已长时间没有新版本。

image6.jpg

Alchemy[9]。这是最有希望的候选者。它提供了一个易于使用的编程模型,在.NET框架上工作,理论上甚至可以使Windows Mobile智能手机(如Motorola MPX 200)成为集群中的节点。它包含出色的示例并不断发展。不幸的是,在我测试期间,一些示例崩溃并在节点上留下孤立进程。

因此,在我的搜索失败之后,我决定自己开发一个库。

hxGrid

在开发过程中,我考虑了以下附加要求

  • TCP/IP 10-1000 MBit 局域网用于数据传输;
  • 网络被认为是内部和安全的。即,该库不包含任何身份验证协议。一个好的安全系统将大大增加开发时间并使库的使用更加复杂;
  • “发送任务时间”/“完成任务时间”比率为0.1或更高。典型任务数据大小可为0.001-100MB;
  • 本地网络中总有工作站在线,可作为应用程序协调器(管理器);
  • 该库将用于离线(最长数小时)和半实时(最长数秒)任务;
  • 单个工作站在任何给定时间段内只能运行一个网格应用程序(这是当前版本的限制);
  • 出于调试目的,单个工作站应能够同时运行代理、协调器和网格应用程序;
  • 当工作站上线时,它应该能够加入已运行的网格会话;
  • 当工作站离线时,这不应中断整个过程。hxGrid 只是将未完成的任务发送到其他节点;
  • 集群可以同时从多个工作站使用。计算资源应平均分配;
  • 网格代理应能够使用每个工作站上的所有CPU核心;
  • 该库应可用于C++和Delphi。

为了最大限度地缩短开发时间,我选择用Delphi实现该库。该库使用伪COM接口[11]、Jedy Visual Code Library[15]和ZLIB[27]。

集群安装

应下载二进制包以安装集群[10]。

image17.gif

集群软件由三个组件组成

  1. 协调器协调器应安装在始终在线的工作站上。协调器负责维护计算资源列表并将其分发给网格用户。协调器在局域网中只能安装在一个工作站上。

    代理和用户可以通过在本地网络上发送广播包来找到协调器,因此如果协调器移动到具有不同IP地址的PC,则无需进行任何配置。

    用户只连接到协调器以获取代理列表。然后,用户直接连接到代理,这与协调器将任务分发给代理的其他网格解决方案不同。这允许用户和代理之间实现最快的通信。
  2. 代理代理应安装在本地网络中的每个工作站上。代理允许使用工作站的空闲CPU时间。
  3. 用户。网格用户是使用hxGrid库编写的应用程序。

hxGrid编程

hxGrid 允许在集群工作站上运行任务(过程)。网格应用程序将任务放入队列并等待它们完成。任务的输入数据写入流 (IGenericStream inStream)。任务成功执行后,输出数据也从流 (IGenericStream outStream) 接收。如有必要,执行任务的代理可以从网格应用程序请求额外数据。

每个任务都是一个具有以下签名的函数

C++

typedef bool (__cdecl TTaskProc)(IAgent* agent, DWORD sessionId, 
                  IGenericStream* inStream, IGenericStream* outStream);

Delphi
type TTaskProc = function(agent: IAgent; sessionId: DWORD; 
         inStream: IGenericStream; outStream: IGenericStream): boolean; cdecl; 

其中 sessionId 是一个唯一的会话 ID。此 ID 是从网格应用程序请求额外数据(稍后描述)所必需的。如果中断(稍后描述),函数应返回 false

此类过程应放置在DLL中。hxGrid 通过将DLL传输到其他工作站来传递代码。TTaskProc 函数也应是线程安全的。代理成功执行任务后,库将输出流发送回用户并调用 FinalizeTask() 回调

C++

typedef void (__cdecl TFinalizeProc)(IGenericStream* outStream); 

Delphi
type TFinalizeProc = procedure(outStream: IGenericStream); cdecl;

此函数也应是线程安全的。

如果一个单线程应用程序正在处理一个大型独立元素数组,那么提高性能的明显方法是将数组分成小范围并在集群节点上处理每个范围。

工作原理

启动时,每个代理通过局域网发送广播包来寻找协调器。与协调器建立连接后,代理开始定期发送其状态(空闲CPU时间百分比、空闲物理内存量、排队任务数量)。

要开始使用集群,网格应用程序会初始化 hxGrid 库。该库连接到协调器(如果需要,通过发送广播包),请求代理列表并直接连接到它们。从这一刻起,该库就可以执行远程任务了。同时,该库继续请求代理列表,因此新的代理能够加入正在运行的会话。

网格应用程序随后将任务添加到队列中。该库在后台线程中不断地将任务发送给代理。如果与某个代理的连接丢失,该库能够将其任务重新分配给其他代理。

当库被反初始化时,它会断开与代理的连接。

为了提高效率,采用了以下技术决策

  • 为了弥补网络通信的时间延迟,库会提前将下一个任务发送给每个代理;
  • 在某些情况下,库可以将已在某个代理上运行的任务副本发送给其他空闲代理。如果某个代理速度慢或当前没有空闲CPU时间,这有助于更快地完成工作;
  • 该库跟踪代理上的空闲CPU时间和空闲物理内存量。如果代理没有足够的空闲CPU时间(百分比在设置中指定)或空闲物理内存少于存储输入流所需(因子在设置中配置),则该库不会向代理发送任务;
  • 该库控制任务队列的长度和输入流的大小。如果排队输入流的总大小超过配置值,该库将阻止应用程序执行,直到至少一个任务完成;
  • 如果选中特殊选项,库可以将任务队列交换到磁盘以最大程度地减少内存使用;
  • 在通过网络发送大流之前,它会使用ZLIB进行压缩(大小阈值在设置中指定);
  • 代理在会话期间在本地缓存DLL;DLL不会被重新发送以再次执行相同任务;
  • 如果代理在10秒内没有空闲CPU时间,这意味着用户已启动一个资源密集型应用程序。在这种情况下,代理会暂停所有工作线程25秒。这是必要的,因为否则当检测到CPU饥饿时,Windows调度程序会将120毫秒的CPU时间分配给具有IDLE优先级的DPC线程[12]。如果线程没有被暂停,这可能会导致游戏中FPS卡顿等问题。
image8n.jpg
PlayStation 2 集群[16]。

库使用

该库以两个DLL的形式发布:hxGridUserDLL.dllzlib.dll。要开始会话,网格应用程序应创建 IGridUser 对象(请参阅 examples\picalculator\C++examples\picalculator\Deplhi

C++

#include "hxGridInterface.h"

void main()
{
 IGridUser* user = CreateGridUserObject(IGridUser::VERSION);
 ...

Delphi
Uses T_GenericStream, I_GridUser;

var
 user: IGridUser;

begin
 IGridUser_Create(user);
 ...

从此刻起,就可以在集群节点上执行任务

C++

IGenericStream* stream = CreateGenericStream();
DWORD d=1+i*9;
stream->Write(&d,4);
user->RunTask("picalculator_task.dll","RunTask",stream,Finalize,&d,true);

Delphi
//write input data to stream
stream := TGenericStream.Create();
d := 1+i*9;
stream.write(d,4); 
//add task to queue
user.RunTask('picalculator_task.dll','RunTask',stream,Finalize,d,true);
//now library owns stream, release our ref 
pointer(stream):=nil;

RunTask() 方法具有以下参数

  • 包含函数代码的DLL文件名;
  • 函数的符号名称(函数应通过名称从DLL导出);
  • 输入流。库将接收流对象的所有权;
  • 完成回调地址;
  • 接收任务唯一ID的变量地址;
  • 阻塞标志。

如果任务不能立即添加到队列中(由于队列长度或队列输入流大小的限制),并且设置了阻塞标志,则该方法将不会返回,直到任务添加到队列中。否则,该方法将返回S_FALSE,应用程序可以使用User->WaitForCompletionEvent()等待机会。

请注意,一些依赖的DLL,例如VC++运行时库,可能在远程工作站上缺失。建议使用静态库构建,并始终使用dumpbin实用程序检查依赖项。要将额外的DLL发送到工作站,它们的名称应在RunTask()方法中指定,并用逗号分隔

user.RunTask('GridGMP_task.dll,GMPPort.dll','RunTask',stream,Finalize,d,true);

任务函数应定期调用以下代理方法,以允许代理处理中止和暂停事件
Delphi

if (agent.TestConnection(sessionId)<>S_OK) then
 begin
  result := false;
  exit;
 end;

这是一种非常快的方法,即使以~10000Hz的频率也可以从内部循环中调用。

User->WaitForCompletion() 用于等待所有排队任务完成。

要关闭会话,应用程序应销毁 IGridUser 对象
C++

user->Release(); 

Delphi
user:=nil;

这简短的一节展示了使用hxGrid所需的一切。

image9.jpg
XBOX 集群[17]。

请求用户提供额外数据

在某些情况下,在代理上运行的任务必须访问会话中所有任务共用的一些全局数据。如果全局数据大小很大,则在任务输入流中传递全局数据效率低下。代理提供以下方法从用户请求全局数据
C++

virtual HRESULT __stdcall GetData(DWORD sessionId, const char* dataDesc, 
          IGenericStream** stream) = 0;

Delphi
function GetData(sessionId: DWORD; dataDesc: pchar; 
                 var stream:IGenericStream): HRESULT; stdcall;

其中
dataDesc – 数据的符号标识符,例如“geometry”;
stream – 接收包含数据流地址的变量的地址(流由代理创建,应由任务释放)。

要处理请求,hxGrid 应用程序应注册以下回调

C++
typedef void (__cdecl TGetDataProc)(const char* dataDesc, IGenericStream** outStream);

user->BindGetDataCallback(GetDataCallback);

Delphi
type TGetDataProc = procedure(dataDesc: pchar; var stream: IGenericStream); cdecl;

User->BindGetDataCallback(callback: TGetDataProc); stdcall;

TGetDataproc 应该创建流并用数据填充它。流的所有权传递给库。通常,任务会尝试访问某些全局缓存,如果数据不在那里,则从 hxGrid 应用程序请求数据。缓存应使用 sessionId 和数据的符号描述进行索引。这些方法在 examples\normalmapper-3-2-2 中用于请求高多边形网格八叉树。

image16.jpg
PlayStation 2 集群[16]。

附加方法

C++

virtual void __stdcall GetSettings(TGridUserSettings settings);
virtual void __stdcall SetSettings(TGridUserSettings settings);

Delphi
User.GetSettings(var settings: TGridUserSettings); stdcall;
User.SetSettings(var settings: TGridUserSettings); stdcall;


这些方法允许以编程方式更改设置。例如,如果应用程序用压缩数据填充流,则可以禁用流压缩。

C++

virtual HRESULT __stdcall CompressStream(IGenericStream* stream);

Delphi
User.CompressStream(stream: IGenericStream): HRESULT; stdcall;


此方法允许流压缩。例如,提前准备和压缩全局数据流是一个好习惯,这样每次调用 GetDataCallback() 时,库就不必再次压缩流。库能够通过检查流的签名来判断流是否已压缩。压缩流不应由任务解压缩,因为代理会自动完成此操作。请参阅 examples\normalmapeer-3-2-2

C++

virtual HRESULT __stdcall FreeCachedData(DWORD sessionId, const char* dataDesc);

Delphi
HRESULT __stdcall IAgent::FreeCachedData(DWORD sessionId, const char* dataDesc) = 0;
br />
代理会话期间请求的全局数据。如果已知数据在会话期间不会再次请求,此方法允许释放缓存以最小化内存使用。例如,如果一个任务请求全局数据,从中构建一些结构,并将它们存储在某个自定义全局缓存中,则其他任务可以使用此缓存中的结构,并且在会话期间不再调用 agent.GetData()

C++

typedef void (__cdecl EndSession)(IAgent* agent, DWORD sessionId);

Delphi
type TEndSessionProc = procedure(agent: IAgent; sessionId: DWORD); cdecl;


代理在会话结束时调用 EndSession() 回调。通常此回调用于释放全局会话数据缓存。回调方法应从 IGrudUser->RunTask() 中指定的 DLL 中按名称导出。此回调是可选的。

取消

使用该库有两种模式。

  1. 任务通过 IGridUser->RunTask(blocking = true) 添加到队列中,然后应用程序通过 IGridUser->WaitForCompletion() 等待完成。
  2. 任务通过 IGridUser->RunTask(blocking = false) 添加到队列中。如果方法因为队列已满而返回 S_FALSE,应用程序将通过 IGridUser->WaitForCompletionEvent() 等待队列继续。当所有任务都已提交时,应用程序通过定期调用 IGridUser->IsComplete() 等待任务完成。


Delphi
for s:=1 to 200 do
  begin 
   ... 
   while (user.RunTask('GridGMP_task.dll,GMPPort.dll',
                      'RunTask',stream,Finalize,d,false)<>S_OK) do 
     begin 
       Application.ProcessMessages();
       user.WaitForCompletionEvent(100);
       if state=ST_CANCELING then 
         begin 
           break; 
         end; 
     end; 
   if state=ST_CANCELING then break;
   ... 
  end; 
... 
bl:=true;
while (bl) do
  begin 
    Application.ProcessMessages();
    Sleep(100); 
    if (state=ST_CANCELING) then
      begin 
        user.CancelTasks();
        break; 
      end; 
    user.IsComplete(bl);
  end;

应用程序可以通过调用 IGridUser->CancelTasks() 随时取消所有任务。

尽管第二种模式更为复杂,但它具有以下优点

  • 应用程序在等待时可以执行一些任务,
  • 可以取消任务,并且
  • 能够在UI中显示进度。

有关第二种模式的详细信息,请参阅 examples\GridGMP\

库使用示例

PI 计算器 (examples\PICalculator)

这是最简单的示例,是计算指定精度的 Pi 值。每个任务计算 [n…n+8] 个数字。

image11n.gif

Mandelbrot (examples\mandelbrot\)

一个Mandelbrot分形浏览器。该应用程序有三个版本

image13.jpg


SingleCPUExtended – 此示例使用FPU本地10字节浮点类型,并在单个CPU上运行。精度不足无法实现高缩放级别。

SingleGMP – 此示例在单个CPU上使用256字节的大数。使用GMP[13]库的Delphi移植版本。

GridGMP – 该示例使用256字节的大数hxGrid。每个代理被分配计算图像的一条垂直线。此应用程序还展示了分区不佳的计算如何降低整体性能:垂直线可能需要非常不同的处理能力,比例高达50:1。更好的解决方案是为每个代理分配计算图像水平扫描的每N个像素。

Normalmapper (examples\normalmapper-3-2-2\)

image12.jpg


ATI NormalMapper 3.2.2[14],移植到 hxGrid

Normalmapper 是一个理想的分布式计算应用程序。法线贴图的每个纹素都可以独立于其他纹素进行计算。启用遮挡项和弯曲法线的法线贴图的总计算时间可能长达12小时。修改后的版本形成计算100个法线贴图像素的任务。每个代理通过调用 IAgent->GetData() 请求序列化的高多边形网格八叉树。八叉树的大小可达300MB,因此在任务输入流中发送它效率低下。此外,修改后的版本包含一个互斥体,它允许一次只运行一个 NormalMapper 实例。可以运行多个具有不同模型的实例,它们将自动排队运行。

需要说明的是,该库不保证任务完成的顺序。由于相交映射和边界纹素的原因,严格按顺序计算纹素非常重要,因此应用程序会执行特殊过程以按所需顺序更新法线贴图纹素。

如何为 hxGrid 安装 ATI NormalMapper

系统要求

几台运行Windows 2000/XP SP1/SP2/Vista的PC,通过局域网连接。

  1. 下载 hxGrid Coordinator[10] 并安装在局域网中的任何工作站上。Coordinator 只应安装在局域网中的一个工作站上。这台PC应始终可用于协调 hxGrid
  2. 下载 hxGrid Agent[10] 并安装在局域网中的所有工作站上。运行代理的PC越多,hxGrid应用程序的工作速度就越快。
  3. 下载 ATI Normalmapper for hxGrid[10] 并安装在局域网中的任何PC上。

无需额外配置。

提示:如果以上所有都安装在单个工作站上,并且它没有带有IP地址的网卡,则可以安装“Microsoft 环回适配器”并分配一个IP地址。

结果

工作站配置

  1. 英特尔奔腾 D Presler 3.0GHz
  2. 英特尔奔腾 4 2.8 GHz (HT)
  3. 英特尔酷睿 2 双核 1.8GHz
  4. 英特尔酷睿 2 双核 2.13GHz
  5. 速龙 X2 3600+
  6. LG GE 笔记本电脑 (英特尔赛扬 M 1.4 GHz)

测量是针对 car_low.nmf + car_high.nmf 网格的计算进行的,大小为 4096 x 4096,弯曲法线,环境光遮蔽。

Normalmapper.exe –on carlow.nmf carhigh.nmf 4096 4096 test.tga

为了实验,ATI Normalmapper 的原始版本使用 VS 2005 进行了重新编译,并进行了激进优化(无 PGO)。

实验表明,即使在单个工作站上(代理、协调器和网格应用程序都在单个工作站上运行),hxGrid 也能成功地利用多核CPU的优势。

image14a.gif
法线贴图计算,小时

image15a.gif
性能提升,倍数

换句话说,在集群上计算法线贴图花费了8分钟,与单台PC上将近4小时相比(性能提升27.4倍)。HT CPU显示出预期的12%速度提升,但我无法解释Core 2 Duo的35%速度提升。(我预计约80%)。可能,这里的瓶颈是共享L2缓存,因为Pentium D Presler显示出73%的速度提升。

image10n.gif
集群状态(工作日结束)

能耗

由于能耗高,集群机房应配备通风和冷却设备。当代理处于活动状态时,办公室的能耗会增加多少?

image18.gif
单个节点的能耗,瓦特

100% 处理器负载大约增加 30 瓦的能耗。我的测量精度可能有待商榷,但即使在拥有 50 个工作站的办公室中,总能耗也仅增加 1.5KW,这比一个好的茶壶(1.8KW)还要少。

已知问题

以下问题尚未解决

  • 由于无法在服务器端关闭连接(Delphi TServerSocket 组件的“特性”),断开与网格的连接可能需要长达 30 秒。在使用分布式 Normalmapper 时这不是问题,但当 hxGrid 用于解决半实时任务时,这可能成为一个问题。如果有人能帮我解决这个问题,我将不胜感激[29]。
  • StartSession()/EndSession() 回调尚未实现;
  • 没有简单的错误处理和调试支持。

使用 hxGrid 的公共应用程序

目前,xNormal[29]正在使用hxGrid加速法线贴图生成。

链接

历史

  • 版本 1.09d - 修复了一个严重错误:如果任务耗时超过20秒,则无法完成最后几个任务;
  • 版本 1.09c - hxGridUseragent 在此版本中进行了更改。
    - 新选项:'allowDiscardCoordinatorIP' - 有关说明请参见 hxgrid.ini
    - 修复了错误:如果单个任务大小大于 hxGridUser 允许的内存使用量,hxGridUser 无法运行任务。
  • 版本 1.09b - 添加了方法 IGridUser->GetConnectionStatus()
  • 版本 1.09a - 添加了 Windows 2000 支持;
  • 版本 1.09 - 这是一个非常稳定的版本,支持 Windows Vista。
    - 修复了严重 bug:代理中的内存损坏;
    - 修复了 scktcomp.pas Delphi 组件中的严重 bug(线程安全问题);
    - 修复了严重 bug:代理无法在 Windows Vista 中加载任务 DLL;
    - 修复了 agentgriduser 中的线程安全 bug(AddRef()/Release() 应该使用 InterlockedXXX);
    - 修复了 agent 中的内存泄漏(IdUDPServer 未释放);
    - 修复了 IGridUser->GetSettings()IGridUser->SetSettings() .h 头文件;
    - IGridUser->isComplete(var complete:boolean) 现在在没有任务运行时返回 TRUE(之前为 FALSE);
    - hxgriduseddll.dllPICalculator C++ 示例中已更新;
    - 修复了 AutoUpgrade 示例:由于与当前目录的相关性而无法升级;
    - 修复了 GridGMP 示例中的内存泄漏;
    - 修复了 DebugWrite 错误:无效字符串导致代理失去连接;
    - 修复了 agent.filecache 中可能的线程安全问题;
    - 新方法:IAgent->GetSessionCacheDirectory()
    - 新方法:IAgent->GetGlobalCriticalSection()
    - 新的 IGridUser 设置:failed_agent_suspend_timeout
    - IAgent.h 中的 TAgentSettings 已更新;
    - IAgent.hI_Agent.pas 已用英文文档化;
    - Normalmapper_hxGrid_Setup 安装 3DS MAX 和 Maya NMF 导出插件;
  • 版本 1.08a - 文章已翻译成英文;
    - 头文件已用英文文档化;
    - 支持取消;
    - 修复了 IAgent->GetData() 中的错误;
    - 修复了错误:冻结在99.99%;
    - 修复了错误:如果 IAgent->GetData() 失败,任务会丢失;
    - 修复了错误:如果工作站超过25天未重启,hxGrid 应用程序无法工作;
    - 针对大 IAgent->GetData() 大小调整了设置;
    - 修复了 GridGMP 示例(所有绘图都在主线程中完成);
    - 网络和内存过载跟踪:会话开始时,太多代理向用户请求数据。库会跟踪网络和内存负载;
    - GridGMP 示例展示了取消支持和进度显示;
    - ATI Normalmapper 安装程序将 NMF 导出插件安装到 3DS MAX 和 Maya 的插件目录中;
    - ATI Normalmapper 中更准确的进度显示;
    - 包含适用于 Maya 7.0、8.0 和 8.5 的 NMFExport 插件;
    注意:代理和协调器已修改,但具有相同的接口版本。建议升级(请参阅 Examples\Autoupgrade\);
  • 版本 1.08 beta - 首次公开发布;
© . All rights reserved.