应用程序算法在使用GeminiGraph框架时,首先通过构造器进行Graph类的实例化,实例化过程中具体Gemini做了哪些工作,下面来逐行进行介绍。
//构造器
Graph() {
threads = numa_num_configured_cpus(); //返回系统中cpu的数量
sockets = numa_num_configured_nodes(); //返回系统中内存节点的数量
threads_per_socket = threads / sockets; //表示每个cpu分到的内存节点数
init();
}
init()初始化中主要进行了numa相关的初始化,omp相关的初始化,MPI相关的初始化操作。
接下来逐行地详细介绍每一段代码的含义。
void init() {
edge_data_size = std::is_same<EdgeData, Empty>::value ? 0 : sizeof(EdgeData);
unit_size = sizeof(VertexId) + edge_data_size;
edge_unit_size = sizeof(VertexId) + unit_size;
assert( numa_available() != -1 );
assert( sizeof(unsigned long) == 8 ); // assume unsigned long is 64-bit
char nodestring[sockets*2+1];
nodestring[0] = '0';
for (int s_i=1;s_i<sockets;s_i++) {
nodestring[s_i*2-1] = ',';
nodestring[s_i*2] = '0'+s_i;
}
struct bitmask * nodemask = numa_parse_nodestring(nodestring);
numa_set_interleave_mask(nodemask);
omp_set_dynamic(0);
omp_set_num_threads(threads);
thread_state = new ThreadState * [threads];
local_send_buffer_limit = 16;
local_send_buffer = new MessageBuffer * [threads];
for (int t_i=0;t_i<threads;t_i++) {
thread_state[t_i] = (ThreadState*)numa_alloc_onnode( sizeof(ThreadState), get_socket_id(t_i));
local_send_buffer[t_i] = (MessageBuffer*)numa_alloc_onnode( sizeof(MessageBuffer), get_socket_id(t_i));
local_send_buffer[t_i]->init(get_socket_id(t_i));
}
#pragma omp parallel for
for (int t_i=0;t_i<threads;t_i++) {
int s_i = get_socket_id(t_i);
assert(numa_run_on_node(s_i)==0);
#ifdef PRINT_DEBUG_MESSAGES
// printf("thread-%d bound to socket-%d\n", t_i, s_i);
#endif
}
#ifdef PRINT_DEBUG_MESSAGES
// printf("threads=%d*%d\n", sockets, threads_per_socket);
// printf("interleave on %s\n", nodestring);
#endif
MPI_Comm_rank(MPI_COMM_WORLD, &partition_id);
MPI_Comm_size(MPI_COMM_WORLD, &partitions);
send_buffer = new MessageBuffer ** [partitions];
recv_buffer = new MessageBuffer ** [partitions];
for (int i=0;i<partitions;i++) {
send_buffer[i] = new MessageBuffer * [sockets];
recv_buffer[i] = new MessageBuffer * [sockets];
for (int s_i=0;s_i<sockets;s_i++) {
send_buffer[i][s_i] = (MessageBuffer*)numa_alloc_onnode( sizeof(MessageBuffer), s_i);
send_buffer[i][s_i]->init(s_i);
recv_buffer[i][s_i] = (MessageBuffer*)numa_alloc_onnode( sizeof(MessageBuffer), s_i);
recv_buffer[i][s_i]->init(s_i);
}
}
alpha = 8 * (partitions - 1);
MPI_Barrier(MPI_COMM_WORLD);
}
- 2-4:分别是表示顶点数据大小,一个顶点一条边的数据大小,一个顶点两条边的大小。EdgeData为调用graph时传入的类型,sizeof(Empty)==1;VertexId是unsigned(默认为int)类型。edge_unit_size为一个顶点两条边的大小。
- 6-7:assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。numa_available(): 判断你电脑支持不支持numa, 不过貌似linux 2.4以后的都支持;
- 9-16: Graph()构造器中定义sockets = numa_num_configured_nodes();即系统中内存节点的数量。nodestring为"0,1,2,3,4,5,.......,sockets-2,sockets-1,sockets"的递增字符串。
- 15:nodemask 是通过解析节点的字符串列表变成的一个位掩码。
- 16:Numa_set_interleave_mask()将当前任务的内存交错掩码设置为nodemask。所有新的内存分配都在交错掩码中的所有节点上进行分页交错。
- 18:omp_set_dynamic(0);不进行动态分配当前线程组(team)的线程数量。
- 19:在串行代码区调用omp_set_num_threads来设置当前线程组(team)的线程数量
- 20-27:内存分配
- 24,25:Numa_alloc_onnode()在指定的节点上分配内存。size参数将被四舍五入到系统页面大小的倍数。get_socket_id()获取内存节点号
- 28-35:检查每个threads上的内存节点是否分配成功
- 31:Numa_run_on_node()在特定节点上运行当前任务及其子任务。
- 41:获取当前并行进程号并赋值给partition_id
- 42:获取并行进程数量并赋值给partitions
- 43-54:通信数组的初始化; MessageBuffer* [partitions] [sockets]; numa-aware;三维数组。
- 56:用于一个通信子中所有进程的同步,调用函数时进程将处于等待状态,直到通信子中所有进程 都调用了该函数后才继续执行。
其中Server cluster 是服务器集群,例如超算中心;
服务器集群中有多个服务器,比如一台主机 ,即上图中的partition,MPI中获取到的并行进程数量和服务器数量相同,partitions的数值就是服务器集群中服务器的数量;
一个服务器(主机)中有多个槽,即 numa_num_configured_nodes() 返回的系统中槽的数量,也就是sockets;
每一个槽中有多个核心(core),即numa_num_configured_cpus() 返回的系统中core的数量,也就是threads。
由此构建的MessageBuffer [partitions] [sockets] [threads]; numa-aware;三维数组。
本人是小菜鸟一枚,如有错误,欢迎在评论区讨论。