调整记录数组大小的域挂起

Domain resizing on an array of records hangs

我有一个包含两个数组的超图数据结构,一个用于边,一个用于顶点(类似于二分图)。我在调整数组大小时遇到​​问题,所以我尝试了简化示例:

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方法没用。它确实会更改域,但不会更改成员数组。

以下是对您在编辑示例中遇到的问题的回应:

从 1.17 版开始,恐怕您陷入了编译器的黑暗角落,我很遗憾它的存在,但我认为我们可以让您摆脱困境。

从一些背景和重要的上下文开始:从一开始,Chapel 就支持 classes 和记录的构造函数(例如,proc C(...) 用于 class C),但这些是他们的设计很天真,尤其是 w.r.t。通用 classes 和记录。在过去的几个版本中,我们一直在从构造器转向初始化器(例如,proc init(..) 对应 class C)来解决这些限制。

从今天发布的 1.17 版开始,初始化器的状态相当好(例如,我现在将它们用于我编写的所有新代码并且很少发誓),但是如果您既不提供初始化器也不提供构造器 (如您的示例中所示),编译器将创建默认的 constructor(而不是默认的初始值设定项),因此 运行 可能会遇到其中一些 long-standing 问题。对于 1.18 版,目标是让编译器默认创建初始化程序并完全弃用构造函数。

因此,这里有一些方法可以解决 EDIT 中较小的测试程序的问题,在 Chapel 1.17 版中,所有这些似乎都为我产生了正确的输出:

1) 使 class 不那么通用。在这里,我给 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)(不得已)请求编译器为你创建一个默认初始化器(而不是默认构造函数)。这种方法确实不适用于 end-users,并非适用于所有情况,并且将来会消失,但同时了解它可能会很方便。在这里,我将附注附加到 class 以告诉编译器创建默认初始化程序而不是默认构造函数。尽管默认情况下编译器不会创建默认初始化程序,但对于许多 classes 和记录,如果您要求它,它就可以创建,这恰好是其中之一:

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);

为了 space,我在这里只解决了你的较短示例,而不是你的较长示例,但希望这些技术也能帮助它(并且很乐意花更多时间如果需要,可以使用更长的那个)。

以下是对您在原始示例中报告的问题的回应(与您的 EDIT 示例中的问题不同,单独回答):

您的原始代码失败的原因是 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 开始)重新分配数组时,其元素首先是默认值 constructed/initialized,然后再分配。因此,同步的 lock$ 字段默认初始化为 true,然后在复制数组元素时,赋值运算符尝试 re-assign 该字段,但它已经满了。

一个解决方案似乎允许您的代码编译并且 运行 对我来说是实现您自己的赋值函数,这样它就不会触及 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。 initialization/assignment 语义 w.r.t。数组大小调整,请随意提交一个 Chapel GitHub issue against it.