我有一个超图数据结构,有两个数组,一个用于边,一个用于顶点(类似于二分图)。我有一个调整数组大小的问题,所以我尝试了简化的例子:
ar dom = {0..0};
var arr: [dom] int;
writeln(arr);
dom = {0..#2};
writeln(arr);
dom = {0..#1};
writeln(arr);
record Vertex {}
record Edge {}
record Wrapper {
type nodeType;
type idType;
var id: idType;
}
record NodeData {
type nodeIdType;
var ndom = {0..-1};
var neighborList: [ndom] nodeIdType;
proc numNeighbors() return ndom.numIndices;
var lock$: sync bool = true;
// This method is not parallel-safe
proc addNodes(vals) {
lock$; // acquire lock
neighborList.push_back(vals);
lock$ = true; // release the lock
}
proc readWriteThis(f) {
f <~> new ioLiteral("{ ndom = ") <~> ndom <~> new ioLiteral(", neighborlist = ") <~> neighborList <~> new ioLiteral(", lock$ = ") <~> lock$.readFF() <~> new ioLiteral(" }");
}
}
type vType = Wrapper(Vertex, int);
type eType = Wrapper(Edge, int);
var dom1 = {0..0};
var arr1: [dom1] NodeData(eType);
writeln(arr1);
dom1 = {0..#2};
writeln(arr1);
dom1 = {0..#1};
writeln(arr1);
当我尝试运行此代码时,它会挂起以下输出:
$ ./resize -nl 1
salloc: Granted job allocation 15015
0
0 0
0
{ ndom = {0..-1}, neighborlist = , lock$ = true }
因此,对整数数组进行大小调整非常合适,但我无法调整我的记录数组。我做错了什么?
作为旁注,当我尝试在完整代码中调整域名大小时,我看到域名发生了变化,但我使用域名的阵列根本没有变化。至少代码不会挂起。
修改
我尝试了另一个更好地阐明原始问题的例子:
class Test {
var dom;
var ints: [dom] int;
proc resize(size) {
dom = {dom.low..size};
}
}
var test = new Test(dom = {0..-1});
writeln(test);
test.resize(1);
writeln(test);
以下是我看到的输出:
$ ./resize -nl 1
salloc: Granted job allocation 15038
{dom = {0..-1}, ints = }
{dom = {0..1}, ints = }
salloc: Relinquishing job allocation 15038
所以我的问题是resize
方法没用。它确实会更改域,但不会更改成员数组。
答案 0 :(得分:4)
以下是您在编辑示例中看到的问题:
我担心你会在版本1.17中陷入编译器的黑暗角落,我很遗憾它存在,但我认为我们可以让你摆脱它。
从一些背景和重要的背景开始:从一开始,Chapel就支持类和记录的构造函数(例如,proc C(...)
为class C
),但这些在设计上是天真的,特别是w.r.t.通用类和记录。在过去的几个版本中,我们已经从构造函数转移到初始化函数(例如proc init(..)
为class C
)以解决这些限制。
截至今天发布的版本1.17,初始化程序处于相当不错的状态(例如,我现在将它们用于我编写的所有新代码并且很少发誓),但是如果你既不提供初始化器也不提供构造函数(如你的例子),编译器将创建一个默认的构造函数(而不是默认的初始化程序),因此可能会遇到一些长期存在的问题。对于版本1.18,目标是让编译器默认创建初始化程序并完全弃用构造函数。
所以,这里有一些方法可以解决EDIT中较小的测试程序的问题,所有这些似乎都在Chapel 1.17版本中为我提供了正确的输出:
1)使类不那么通用。在这里,我给dom
字段一个初始值,这样编译器就可以确定它的类型,这显然有助于它使用默认构造函数,足以生成预期的输出:
class Test {
var dom = {0..-1};
var ints: [dom] int;
proc resize(size) {
dom = {dom.low..size};
}
}
var test = new Test(dom = {0..-1});
writeln(test);
test.resize(1);
writeln(test);
2)编写显式初始化程序。在这里,我将离开dom
泛型,但会创建一个初始化程序,指定它以匹配new
调用的签名:
class Test {
var dom;
var ints: [dom] int;
proc init(dom: domain(1)) {
this.dom = dom;
}
proc resize(size) {
dom = {dom.low..size};
}
}
var test = new Test(dom = {0..-1});
writeln(test);
test.resize(1);
writeln(test);
3)(最后的手段)请求编译器为您创建默认初始值设定项(而不是默认构造函数)。这种方法实际上并不适用于最终用户,不适用于所有情况,并且将来会消失,但在此期间可能会很方便。在这里,我将一个pragma附加到类中,告诉编译器创建一个默认的初始化器而不是默认的构造函数。虽然默认情况下编译器不会创建默认初始值设定项,但是对于许多类和记录,如果你要求它,它就可以了,这恰好是其中之一:
pragma "use default init"
class Test {
var dom;
var ints: [dom] int;
proc resize(size) {
dom = {dom.low..size};
}
}
var test = new Test(dom = {0..-1});
writeln(test);
test.resize(1);
writeln(test);
为了太空的利益,我在这里只提到了你较短的例子,而不是你较长的例子,但希望这些技巧也能帮助它(如果需要的话,我很乐意花更多的时间在更长的时间)
答案 1 :(得分:3)
以下是对您在原始示例中看到的问题的回应(与您的编辑示例中的问题不同,单独回答):
原始代码失败的原因是Chapel为记录创建的默认分配。具体来说,给出记录R
:
record R {
var x: t;
var y: t2;
var z: t3;
}
如果你没有在R
之间创建一个赋值,编译器将创建一个通用形式:
proc =(ref lhs: R, rhs: R) {
lhs.x = rhs.x;
lhs.y = rhs.y;
lhs.z = rhs.z;
}
在您的示例中,这会导致问题,因为当重新分配数组时,当前(从Chapel 1.17.0开始),其元素首先默认构造/初始化然后分配。因此,同步的lock$
字段将默认初始化为true
,然后在复制数组元素时,赋值运算符会尝试重新分配该字段,但它已经已满。
一个似乎允许你的代码为我编译和运行的解决方案是实现你自己的赋值函数,使它不会触及lock$
字段:
proc =(ref lhs: NodeData(?t), rhs: NodeData(t)) {
lhs.ndom = rhs.ndom;
lhs.neighborList = rhs.neighborList;
// let's not touch lhs.lock$
}
但是你应该考虑这是否对你的用例是正确的锁定纪律。如果你认为某些事情需要在这里改变w.r.t.初始化/赋值语义w.r.t.数组调整大小,请随意提交教堂GitHub issue against it。