Aggregator在list_execute
s过程中聚合数值。一个aggregator就像是整个集群中的一个全局变量。
头文件: #include "lib/dcaggregator.hpp"
创建一个aggregator: Husky::Aggregator<ValueType> agg(InitValue, [](ValueType & old, const ValueType & new) { ... });
. 这里的 ValueType
是将被聚合的数值的数据类型。Aggregator的构造函数需要提供两个参数: InitValue
和一个 lambda. InitValue
是需要聚合的原始数据而lambda是表明要以什么样的方式来聚合(即聚合规则)。 Lambda 必须包含两个参数, 其一是ValueType
的引用,另一个是它的const引用。在lambda函数体内,你需要指明旧的数据怎么变化获得新的数值。(注意:请在你的job函数中创建你的aggregators)
你可以使用aggregator的成员函数update
:agg.updata(V)
来聚合一个值。其中这里的V指的是一个'Valuetype'类型的值。
需要知道的是,仅仅使用updata
的话只会在本地做聚合。你还需要一个全局的aggregation来得到最后的聚合值。然而在这之前,请确保你已经至少使用了一次list_execute
运算。
最后你可以使用agg.get_value()
来得到你最后的聚合值。(注意:成员函数'get_value()'返回的是一个线程共享的变量引用,这样如果其中的一个worker改变了这个引用值,其他workers就可能会受到影响。)
[额外] Aggregator提供两种选择: 即AGG_RESET_EACH_ROUND
和AGG_KEEP_AGGREGATE
. (它们是代码中全局定义的const变量) AGG_RESET_EACH_ROUND
意味着, 在每次迭代中, 这个aggregator将在接受任何新的更新之前先重置它的值。而AGG_KEEP_AGGREGATE
则意味着这个aggregator值是每次迭代更新后的aggregation。 在默认情况下,系统使用的是AGG_KEEP_AGGREGATE
,但是我们可以通过使用`agg.set_agg_option(AGG_RESET_EACH_ROUND)' 来改变这个选项。
//0. 头文件 #include "lib/dcaggregator.hpp" void job() { //1. 创建 aggregator Husky::Aggregator<int> sum(0, [](int & a, const int & b){ a += b; }); //6. [可选] 重置 agg 选项 sum.set_agg_option(AGG_RESET_EACH_ROUND); //3. 确保操作了一次 list_execute 以便进行全局的aggregation worker.list_execute(obj_list, [&](OBJ & obj) { //2. 更新 aggregator sum.update(1); }); //4. 获得 aggregation 值 Husky::log_msg(std::to_string(sum.get_value())); }
有时候你并不想让aggregator每一轮都保持更新,因为这样需要为聚合增加额外的通信。这里提供了一个交换器可以让你控制aggregation的打开与闭合。
Husky::AggregatorWorker::get().AGG_enable(AGG_ON); // 打开 // Or ... Husky::AggregatorWorker::get().AGG_enable(AGG_OFF); // 关闭
注意:一旦你关闭了交换器,在list_execute中的全局aggregation过程都将被忽略,所有的aggregators都将不会被更新。如果你没有创建aggregators,那么即使交换器是打开的,在每一次list_execute时都没有执行全局aggregation的过程。
如果你想要对std::vector
使用aggregator,请注意如下要点:
std::vector
。在你将std::vector
输入构造函数之前,请确保向量已被resize。std::vector<double> init_para; // 默认大小是0! init_para.resize(10); // 假设向量的长度是10 // 或者: std::vector<double> init_para(10); Husky::Aggregator<std::vector<double> > para(init_para, [](std::vector<double> &a, const std::vector<double> &b) { for (int i = a.size() - 1;i >= 0;i--) a[i] += b[i]; } );
update
函数:int idx = 3; // 在指标3 下面的值将被更新 para.update(std::make_pair(idx, val), [](std::vector<double> &a, const std::pair<int, double> & update) { int idx = update.first; double val = update.second; a[idx] += val; // you know these three lines can be written in one line, right } }; // 假设我仅想要更新在指标`idx`下面的值
这个updata
函数需要第二个参数,是用来指明怎么使用所给的值来更新向量的一个lambda.
update
函数,那么你就可能需要给aggregator的构造函数赋予个zero value
,也就是一个lambda:std::vector<double> init_para(10); Husky::Aggregator<std::vector<double> > para( init_para, // initial value [](std::vector<double> &a, const std::vector<double> &b) { // aggregation 规则 for (int i = a.size() - 1;i >= 0;i--) a[i] += b[i]; }, [=](std::vector<double> &a) { // 怎么设置一个向量为零 a = init_para; } );
你可能注意到了在构造函数中还提供了第三个参数。 这个lambda指明怎么改变一个向量为zero
。什么是'zero', 这跟aggregation的过程有关。
简单来说,aggregation包括本地的aggregation和全局的aggregation.
在列表执行期间,每一个worker遍历它所拥有的对象,更新aggregator的值。 实际上,每一个worker会保存一个aggregation的副本(本地agg),并且对象导致的更新也会首先写入到本地的agg中。所以如果在job
函数当中定义了一个aggregator并且存在有10个workers,那么便会有10份副本分别保存来自每台本地worker上的对象得到的更新值。
在所有的对象都被迭代后,所有本地aggs的更新都将会被发送至本地某个特定的agg中,并由这个agg来做全局的aggregation。完成全局aggregation后, aggregation的结果就被发送回每一台机器。这个结果将被同一台机器上的workers共享。
这里我们看到有本地aggregation和全局aggregation。在agg构造函数中给的初始值用于全局aggregation,而零值则是用于本地的aggregation。