首页 Compiling Distributed C++

Compiling Distributed C++

举报
开通vip

Compiling Distributed C++ Compiling Distributed C++ Harold Carr, Robert R. Kessler, Mark Swanson Department of Computer Science University of Utah Salt Lake City, Utah, 84112 Abstract Distributed C++ (DC++) is a language for writ- ing parallel applications on loosely coupl...

Compiling Distributed C++
Compiling Distributed C++ Harold Carr, Robert R. Kessler, Mark Swanson Department of Computer Science University of Utah Salt Lake City, Utah, 84112 Abstract Distributed C++ (DC++) is a language for writ- ing parallel applications on loosely coupled distributed systems in C++. Its key idea is to extend the C++ class into 3 categories: gateway classes which act as communication and synchronization entry points be- tween abstmct processors, classes whose instances ma y be passed by value between abstract processors via gate- ways, and vanilla C++ classes. DC++ code is com- piled to C++ code with calls to the DC++ runtime system. The DC++ compiler wmps gateway classes with handle classes so that remote procedure calls are transparent. It adds static variables to value classes and produces code which is used to marshal and un- marshal arguments when these value classes are used in remote procedure calls. Value classes are deep copied and preserve structure sharing. This paper shows DC++ compilation and performance. 1 Introduction DC++ is designed to exploit loosely coupled dis- tributed systems built by interconnecting multiple workstations through a local area network. DC++ is a distributed version of C++ [8] (th’ is paper assumes knowledge of C++). DC++ provides a small num- ber of simple extensions to C++: 2 built-in classes: DcDomain and DcThread; and 2 new categories of class instance usage: gateways between domains, and “value” instances which may be passed between do- mains through gateway member function invocation and return. The DC++ language is discussed in [6,5]. This paper shows how DC++ is compiled. We will use the bounded buffer problem [l] as a running example throughout this paper. We include performance mea- surements for this example. 2 Domains - Abstract Processors DC++ supports parallelism by providing 2 types: domains and threads ([13, 171). A domain is a logi- cally encapsulated address and control space, an ab- stract processor. A domain is similar to a monitor [ll]: they ensure mutual exclusion synchronization by en- forcing the rule that only one thread of control may be active in a domain at a time. If another thread at- tempts entry to an occupied domain, that thread will be queued on a FIFO queue for later entry when the domain becomes vacant. It differs from a monitor in that domains may be dynamically created and deleted. Further, a domain by itself does not have any entry points. Entry points may be dynamically created and deleted by creating gateway class instances into d* mains. A domain is created by specifying a physical processor number (@based) on which to allocate the domain. For the bounded buffer example we will cre- ate 6 domains: one each for the 3 producers, one for the buffer, and one each for 2 consumers: const nun-producers = 3; const num-consumers = 2; DcDomain+ pd[num-producersl; DcDomain* cd[num-consumers] ; DcDomainr bd = new DcDomain(num-producers + nu-consumers) ; for (int i = 0; i < nul-producers: ++i) for (i = 0 ; i < nu.-consumers; ++i) pd[i] = new DcDomain(i1; cdci] = new DcDorain(i + nun-producers); Since there may be more domains than actual phys- ical processors, the domain is allocated on i X DcNodeCount (1, where DcNodeCount (1 returns the 1- based number of physical processors available to the specific execution. This means that more than one d e main may be explicitly (by the programmer) or implic- itly (by the domain allocator) created on a processor. The DC++ runtime system supports multitasking so the programmer need not be concerned with these de- tails. 496 1063-6374/93 $03.00 0 1993 IEEE 3 Gateways - Domain Entry Points A gateway is a system-wide unique “pointer” to an object created in a specific domain. I t is treated as an ordinary C++ object: member function invoca- tions on gateway objects result in remote procedure calls (RPCs) if that gateway resides in a different do- main than the domain from which it is invoked. Oth- erwise a vanilla C++ member function invocation re- sults. Since RPCs look identical to vanilla C++ mem- ber function invocations, redistribution of gateways is possible without program modification (modulo syn- chronization characteristics of the algorithm). A gateway class is declared like a vanilla C++ class, except it inherits from DcGateway: class Producer : public DcGateway { int Bufferr buffer; public: Producer(int i, Buffer* b) { nu.-items * i, buffer = b; 1 void Produce0 { num-items; // Number items to produce. // Buffer to put them in. while (num,items--) { 1 buffer->Deposit(new Derived(num,items)); 1 1: class Buffer : public DcGateway { unsigned max-items; // Capacity of buffer. Derived** slots; // Array to contain items. unsigned head; // Where producer puts items. unsigned tail; // Where consumer gets items. unsigned size; // lumber of items in buffer. Buffer(int i){ pub1 ic : head = tail = size = 0; .ax-items = i; slots = new Derived*[max-items] ; MakeDelayQueue(Buffer: :Deposit) ; DQOpen(Buffer: :Deposit) ; MakeDelayQueue(Buffer::Fetch); 1 void Deposit (Derived* i) { slotsCtail] = i; size++; tail - (tail + 1) X max-items; if (size > 0) DQOpen(Buffer: :Fetch) ; 1 Derived* Fetch(){ Derived* retval = slotsChead] ; size-- ; head = (head + 1) % rax-items; if (size < rax-items) DQOpen(Buffer: :Deposit) ; return retval ; 1 1 : class Consumer : public DcGateway int num-items; // Number items to consume. Buffer* buffer; // Buffer to get them from. Consumer(int i, Buffer* b) { nu-items = i, buffer = b; void Consume 0 { public : 1 while (num-item--) { Derived* i = buffer->FetchO ; if (i->DataO == 0) spin0 ; return; 3 1 1: The buffer has explicit delay queues associated with its Deposit and Fetch methods. Delay queues provide condition synchronization. If the delay queue is open, calls to the method proceed as normal. If the delay queue is closed then the calls are queued on a FIFO queue for later entry to the method when the queue is opened. A call (thread) waiting on a method’s delay queue is not considered to have entered the domain. They are used in this example to ensure that producers only deposit items when there is room in the buffer, and that consumers only fetch items when there is one or more available. A gateway instance is created in a specific domain by providing an optional domain argument as the first argument to the gateway’s constructor. If not present the current domain is used: Buffer* b = new Buffer(bd, 8) ; Producer* p[num-producers] ; Consumer* c [nu-consumers] ; for (i = 0; i < nu.-producers; ++i) for (i = 0; i < nu.-consumers; ++i) pci] = new Producer(pd[i], 25, b); c[i] = new Consumer(cdCi], 25, b); Note that the type passed between the producer, buffer and consumer is Derived*. This type is a user defined “value” class and will be discussed later. Also note that the optional domain argument is not de- clared in the user’s gateway class definition. This is handled by the compiler. 4 Threads Concurrency is achieved by creating multiple threads of control. In the bounded buffer example, 497 threads are created for the producers and consumers, whereas the buffer is passively enclosed in a domain void Deposit (Derived* ; Derived* Fetch0 ; to ensure mutually exclusive access to it: 3; for (i = 0 ; i < nu-consumers; ++i) for (i = 0; i < num-producers; ++i) nev DcThread(cCil->ConsumeO) : new DcThread(p[i]->Produce~)) ; For each constructor in the user class, 2 constructors are created in the handle class: one with a type signa- ture identical to the user's definition, and one which adds a domain argument as the first parameter. In this way gateways may be created in the same domain or between domains. The handle class inherits from gateway, whose def- inition is: Threads may return values when they terminate. These values may be used by the thread which created the new thread, and/or the termination of the created thread may be detected by the creating thread. This feature is not used in this example. 5 Compiling Gateways The fundamental idea is to transform the program so that all references to user defined gateway classes are changed to references to compiler generated han- dle classes. Each user gateway class has an associated handle class which intercepts all method invocations. The handle class determines if the gateway method being invoked is for an instance in the same domain as the invoker. If so, it does a normal C++ method invocation. Otherwise, it marshals the arguments and does an RPC to the domain in which the gateway re- sides. Upon return it unmarshals the return value. The compiler generated handle class frees the pro- grammer from these details and allows gateways to be redistributed without program modification. This section shows the details of the gateway compilation process. class gateway { private : DcDomaine domain; DcObject* remote; // remote object void* local; // local object DcDomaint dorainGid0 { return domain; 1 DcObjectt remoteObj0 { return remote; 3 void* local0bjO { return local; 1 void* localPO { return local; 3 gateway(void* v) : domain(NULL), remote(WULL), local(v) gateway(DcDomain* did, DcObjectz oid) : domain(did1, remote(oid1, local(HULL) {1 // domain vhere object lives protected: 1; When creating objects derived from gateway, a gate- way is returned which either points to an instance in the domain or to an instance in a remote domain. When the handle class constructor is given an optional domain it constructs the actual object on a remote node via the compiler generated MakeRemoteOb ject routine: A unique tag is created for all gateway classes in a DcObject+ nakeRe.oteObject(unsigned type){ program: void* v = NULL; enum CLASS-TAGS { Consumer-TAG, Buffer-TAG, Producer-TAG, 1; These tags are used to indicate the type of object being made when that object is created remotely. Each user gateway class is wrapped by a handle class: class Buffer-U : public gateway { public: Buffer-U(int i) Buff er-U (DcDomain* d. int i) : gatewayhew Buffer(i1) C1 switch (type) { case Consumer-TAG : case Buffer-TAG : case Producer-TAG : default : break ; 3 return Registerobject (v, DcThisDomainO) ; v = new Consumer; break; v = new Buffer; break; v = new Producer; break: DcError ("Unknown object type") : 3 Registerobject is a DC++ runtime system routine which places the actual pointer to the newly created - . : gateway(d, object into an OutTable table, a table of entities which may be used remotely. It returns a DcObject* which is a special pointer type which is unique and valid MakeRemoteObject(Buffer-TAG, d, i)) C3 498 between domains. The bits of this pointer indicate the node on which the object resides and the index of the actual object pointer in that node’s OutTable table. This example is incomplete in that it doesn’t show how arguments to constructors are handled remotely, and it only shows one constructor per class. If there is more than one constructor they are distinguished by their type signature. Constructor arguments are handled in a manner similar to arguments to methods (shown below). A tag is created for each public method in the han- dle class (constructor tags not shown in example): enum BufferPTR-METHOD,TAGS { DerivedptrBufferFetch-TAG, VoidBufferDepositDerivedptr-TAG, 3 ; For each method in the user’s original gateway def- inition an associated method is defined in the handle class: void Buff er-U: :Deposit (Derived* a3) { if (1ocalP 0 ) HsgBuf Id f = HakeHsgBuf(100) ; SetHsgBuf (f , 0, remoteobj 0 ; SetHsgBuf(f, 1, voidBufferDepositDerivedptr-TAG); PACK-voidBufferDepoaitDerivedptr-PAMS(f, Oa3); ApplyYithinDomain(REHOTE-Buffer-HANDLER, DeleteMsgM (f ; return ((Buffer-U*)localObj O)->Deposit(aJ) ; f , domainGid0 ; 3 Derived* Buffer-Y::FetchO{ if (localPO 1 HsgBufId f = HakeHsgBuf (2) ; SetHsgBuf (f , 0, remoteobj 0 ) ; SetHsgBuf (f , 1, DerivedptrBuff erFetch-TAG) ; HsgBufId fr = (MsgBufId) return ((Buff er-U*)localObj 0 ) ->Fetch0 ; ApplyUithinDomain(REH0TE-Buffer-HANDLER, f, domainGid0 1 ; DeleteHsgBuf (f) ; Derived* result; UNPACK-DerivedptrBuff erFetch-RETVAL (f r , &result) ; DeleteHsgBuf (fr) ; return result; 3 These handle methods are how transparent RPCs are method. Otherwise it marshals the remote object ref- erence, the method tag, any arguments, and then calls the RPC handler in the domain for that handle class via ApplyVithinDomain. ApplyVithinDomain is the blocking remote procedure call routine in the DC++ runtime system. When the RPC returns, it unmar- shals the return value into a message buffer. The o p eration of compiler generated PACK and UNPACK mar- shaling routines is discussed later. They use runtime message buffers (MsgBuf Id) to contain object refer- ences, method tags, argument values, and return val- A remote method handler is created for each handle class. The method tags are used to dispatch to the appropriate method when handling RPCs: ues. void* REMOTE-Buffer-HANDLER(HsgBufId f) < Buff er* r = (Buff er*) OutTable (HsgBuf (f , 0) ; Buff erPTR-METHOD-TAGS 1 = MsgBuf (f , 1) ; suitch (1) { case DerivedptrBufferFetch-TAG : { DeleteHsgBuf (f) ; Derived* a4 = r->Fetch(); HsgBuf Id fr = return (void*) fr; PACK-Der ivedpt rBuf f erFet ch-RETVAL (&a4) ; 3 case voidBufferDepositDerivedptr-TAG : { DeleteHsgBuf (f ; Derived* a3; UNPACK-VoidBufferDepositDerivedptr-PARW(f, r->Deposit (a31 ; return NULL; Oa3) ; 3 default : 3 DcError (“Unknown method”) ; 3 This routine dispatches to the appropriate method call, marshals the arguments from the message buffer and calls the associated user operation. Return values are placed in a message buffer to be used on the other end of the RPC. 6 Value Objects Systems such as [2] provide primitives for send- ing and receiving data between processes, but require the programmer to pack and unpack aggregate data. Besides the gateway classes, which marshal and un- marshal arguments and return values, DC++ pro- vides “value” classes so that programs may pass deep, achieved. If a Buff er handle instance is created in the same domain as the invoker of its constructor then a pointer to that local object is installed in the gate- way’s local method variable. When a handle method is invoked it first checks to see if the object is local. If so it avoids the overhead of RPC by invoking the local 499 structure-preserving copies of value class instances be- tween domains. A value object is a class which inherits from the built-in DC++ class DcValue: class Base : public DcValue { publ ic : int data; Base(int i = 0 ) : data(i) { 1 int Data0 { return data; 3 3 ; class Contained : public DcValue { public: char c; Contained(char -c = lt') : c(-c) 3 1; class Derived : public Base { Contained mi, *mpl, *mp2; double d; Derived(int -i = 0, publ ic : double -d = 0 . 0 , char cl = 'U,, char c2 = ' x ' ) : Base(-i), mi(c1). d(-d), mpl (neu Contained(c2) 1 , mp2(mpl) C 1 -Derived() { delete mpl; 3 3 ; (Note that member data mpi and mp2 point to the same instance.) Inheriting from DcValue indicates that when one of these objects is passed as an argument to, or re- turn value from a gateway member function, it is to be totally copied. This means that any objects, point- ers to objects, or built-in data types contained within the passed object, must be recursively copied and any structure sharing present via pointers must be pre- served. Any member data objects or member data pointers to objects, must be objects which also derive from DcValue so the compiler can add the necessary support for the complete copy operation. The com- piler handles C++ scalar types automatically. Value objects may be passed-by-value freely be- tween domains via gateway member function argu- ments and return values. This is seen in the exam- ple by the calls to the Buffer methods Deposit and Fetch which accept and return instances of the user defined Derived class. These routines actually pass and return Derived*. In this case, the object is still copied and the pointer to the newly created object on the receiving end is used rather than the original pointer. 7 Compiling Value Objects 7.1 Runtime Type Information The compiler adds static variables and virtual func- tions to each user class which inherits from DcValue: class Base : public DcValue { DECLARE-TYPE(Base) ; int data; Base(int i = 0) : data(i) C 1 int Data0 { return data; 3 publ ic : 3; The DECLARE-TYPE macro: #def ine DECLARE-TYPE ha") \ public: \ virtual const TypeInfot Type0 const \ { return kinfo-obj; > \ static const TypeInfor Info0 \ { return &info-obj; 3 \ virtual void Uriter(ostream&) const ; \ static DcValue* Reader(istream&) ; \ static name* Read(istream& s) \ name (istred) ; \ static const TypeInfo info-obj \ { return (name*)DcValue::Read(s); 3 \ private: ', defines Type and Info methods used to obtain type in- formation at runtime. This information is contained in the private static member data info-obj. Type is used to get this information given any instance which derives from DcValue (e.g., someinstance->Type( 1). Info is used to get this information if the type is known before hand (e.g., Base: :Info()). These are used to obtain typespecific reader functions used when sending objects between domains. 7.2 Sending Objects Over Streams The Uriter, Reader, Read methods and the name(istream&) constructor are used to convert ob- jects to/from linear byte streams for transmission and reception between domains. The compiler generates these for each type that directly or indirectly inherits from DcValue: DEFIIE-TYPE(Base) ; DEFINE-TYPE(Contained); DEFINE-TYPE(Derived); The DEFINE-TYPE macro: #define DEFINE-TYPE(name) \ DcValue* name: :Reader(istream& 8 ) \ 500 { return new name(s); I \ name : :Reader) \ const TypeInfo name: : inf o-obj (STRIIGIFY (name) , \ defines the Reader method which uses the constructor from istream to create an instance of the class given a linear stream of bytes. It also initializes the static info-obj method data to contain the name of the class and a pointer to the class reader. The compiler generates a Writer method and an istream constructor for each value class: Base::Base(istream& s) : DcValueb) { I void Base::Uriter(ostred s) const s << data << endl; 1 Contained::Contained(istream& s) : DcValue (s) { s >> data; s >> c; 1 void Contained::Uriter(ostream& 8 ) const 1 1 Derived: :Derived(istream& s) s << c << endl; : Base(s) , Bib), mpl (Contained: :Read(s) 1, mp2(Contained: :Read(s)) s >> d; I void Derived::Uriter(ostrem& s) const { Base: :Uriter(s); mi.Uriter(s) ; mpl->Urite(s) ; ~p2->Urite(s) ; s << d << endl; I Derived types first call the static Writer for their base classes (only one in this example). Then they write out instances using the Writer method of the instance. Pointers to instances are written using the Write method. Write keeps a table of pointers to pre- serve structure sharing. Built-in data types are writ- ten and read to and from a stream with the normal C++ iostream operators. Data is written and read in identical order. 7.3 Argument Marshaling The compiler generated Buff er-U: :Deposit method receives a Derived* as an argument. It con- verts this to a linear byte stream via the generated PACK-voidBuf f erDepositDerivedptr9ARMS routine. This routine uses the write operations to write into an output string st
本文档为【Compiling Distributed C++】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_141759
暂无简介~
格式:pdf
大小:611KB
软件:PDF阅读器
页数:8
分类:互联网
上传时间:2012-05-22
浏览量:23