Hi all --

This is a request for a review to simplify the implementation of standard operations (+, -, *, etc.) on homogeneous tuple operations. It's pretty straightforward. I thought I was seeing a bit of performance improvement on my Mac last night, but on linux it seems to be a bit of a wash. However, looking at the generated code, it seems like a no-brainer -- it makes it simpler and more straightforward. Patch attached, proposed log message below.

-Brad


----

Simplify the implementation of homogeneous tuple operations


Prior to this commit, we have been implementing all tuple operations (+, -, *, etc.) on singleton tuples, pairs of tuples, and tuple-scalar pairs using a recursive Lisp-y style of implementation approach. While this approach is reasonably elegant to write and very general with respect to the tuple types, the code it generates is pretty ugly. For example, a tuple addition like this:

  var x: 3*real = (1.0, 2.0, 3.0);
  var y: 3*real = (4.0, 5.0, 6.0);

  var z = x + y;

ends up looking like this:

  call_tmp = (1.0 + 4.0);
  ret_to_arg_ref_tmp_ = &wrap_call_tmp;
  chpl__tupleRestHelper2(1.0, 2.0, 3.0, ret_to_arg_ref_tmp_);
  ret_to_arg_ref_tmp_2 = &wrap_call_tmp2;
  chpl__tupleRestHelper2(4.0, 5.0, 6.0, ret_to_arg_ref_tmp_2);
  call_tmp2 = *(wrap_call_tmp + INT64(0));
  call_tmp3 = *(wrap_call_tmp2 + INT64(0));
  call_tmp4 = (call_tmp2 + call_tmp3);
  call_tmp5 = *(wrap_call_tmp + INT64(0));
  call_tmp6 = *(wrap_call_tmp + INT64(1));
  ret_to_arg_ref_tmp_3 = &wrap_call_tmp3;
  chpl__tupleRestHelper(call_tmp5, call_tmp6, ret_to_arg_ref_tmp_3);
  call_tmp7 = *(wrap_call_tmp2 + INT64(0));
  call_tmp8 = *(wrap_call_tmp2 + INT64(1));
  ret_to_arg_ref_tmp_4 = &wrap_call_tmp4;
  chpl__tupleRestHelper(call_tmp7, call_tmp8, ret_to_arg_ref_tmp_4);
  call_tmp9 = *(wrap_call_tmp3 + INT64(0));
  call_tmp10 = *(wrap_call_tmp4 + INT64(0));
  call_tmp11 = (call_tmp9 + call_tmp10);
  *(z + INT64(0)) = call_tmp;
  *(z + INT64(1)) = call_tmp4;
  *(z + INT64(2)) = call_tmp11;

In wrestling through generated code idioms like this for homogeneous tuples, I realized that there's no need to use the recursive approach because it's straightforward to anticipate the result type required, declare a variable for it, and fill the result using a param loop. This results in much cleaner generated code, like this:

  call_tmp = (1.0 + 4.0);
  call_tmp2 = (2.0 + 5.0);
  call_tmp3 = (3.0 + 6.0);
  *(z + INT64(0)) = call_tmp;
  *(z + INT64(1)) = call_tmp2;
  *(z + INT64(2)) = call_tmp3;

The primary challenge to doing the similar thing for heterogeneous tuples is that it's difficult to declare the result type using Chapel code today.
For homogeneous operations, we can say things like:

    var result: a.size*(a(1) + b(1)).type;

because we know that the type resulting from adding the 1st component will apply to all the others as well. But for a heterogeneous case, we can't anticipate how many fields the tuple will have and don't have a way for mapping or expanding type-based computations like this across tuples (at least that I've been able to come up with so far).

So, in this commit, I provide specialized overloads of the operations for the (common) cases when the tuple(s) are homogeneous and preserve the original implementation for the fall-through case when one or both tuples are heterogeneous.

My next step is to look at eliminating the tuple temps that are used when assigning tuples, which result from the combination of normalization and the fact that our assignment operators don't take the LHS by reference. This should go even further in cleaning up tuple-based code.
Index: modules/internal/ChapelTuple.chpl
===================================================================
--- modules/internal/ChapelTuple.chpl	(revision 22445)
+++ modules/internal/ChapelTuple.chpl	(working copy)
@@ -208,7 +208,7 @@
   // tuple except the first
   //
   inline proc chpl__tupleRest(t: _tuple) {
-    proc chpl__tupleRestHelper(first, rest...)
+    inline proc chpl__tupleRestHelper(first, rest...)
       return rest;
     return chpl__tupleRestHelper((...t));
   }
@@ -216,6 +216,14 @@
   //
   // standard overloaded unary operators on tuples
   //
+
+  inline proc +(a: _tuple) where isHomogeneousTuple(a) {
+    var result: a.type;
+    for param d in 1..a.size do
+      result(d) = +a(d);
+    return result;
+  }
+  
   inline proc +(a: _tuple) {
     if a.size == 1 then
       return (+a(1), );
@@ -223,12 +231,26 @@
       return (+a(1), (...+chpl__tupleRest(a)));
   }
   
+  inline proc -(a: _tuple) where isHomogeneousTuple(a) {
+    var result: a.type;
+    for param d in 1..a.size do
+      result(d) = -a(d);
+    return result;
+  }
+  
   inline proc -(a: _tuple) {
     if a.size == 1 then
-      return (-a(1),);
+      return (-a(1), );
     else
       return (-a(1), (...-chpl__tupleRest(a)));
   }
+
+  inline proc ~(a: _tuple) where isHomogeneousTuple(a) {
+    var result: a.type;
+    for param d in 1..a.size do
+      result(d) = ~a(d);
+    return result;
+  }
   
   inline proc ~(a: _tuple) {
     if a.size == 1 then
@@ -237,6 +259,13 @@
       return (~a(1), (...~chpl__tupleRest(a)));
   }
   
+  inline proc !(a: _tuple) where isHomogeneousTuple(a) {
+    var result: a.type;
+    for param d in 1..a.size do
+      result(d) = !a(d);
+    return result;
+  }
+  
   inline proc !(a: _tuple) {
     if a.size == 1 then
       return (!a(1),);
@@ -266,6 +295,28 @@
   //
   // standard overloaded binary operators on tuples
   //
+
+  proc chpl_TwoHomogTuples(t1, t2) param {
+    return isHomogeneousTuple(t1) && isHomogeneousTuple(t2);
+  }
+
+  //
+  // Case optimized for homogeneous tuples
+  //
+  inline proc +(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to + have different sizes");
+
+    var result: a.size*(a(1) + b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) + b(d);
+
+    return result;
+  }
+
+  //
+  // General case for heterogeneous tuples
+  //
   inline proc +(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to + have different sizes");
@@ -274,6 +325,18 @@
     else
       return (a(1)+b(1), (...chpl__tupleRest(a)+chpl__tupleRest(b)));
   }
+
+
+  inline proc -(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to - have different sizes");
+
+    var result: a.size*(a(1) - b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) - b(d);
+
+    return result;
+  }
   
   inline proc -(a: _tuple, b: _tuple) {
     if a.size != b.size then
@@ -283,6 +346,18 @@
     else
       return (a(1)-b(1), (...chpl__tupleRest(a)-chpl__tupleRest(b)));
   }
+
+
+  inline proc *(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to * have different sizes");
+
+    var result: a.size*(a(1) * b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) * b(d);
+
+    return result;
+  }
   
   inline proc *(a: _tuple, b: _tuple) {
     if a.size != b.size then
@@ -293,6 +368,17 @@
       return (a(1)*b(1), (...chpl__tupleRest(a)*chpl__tupleRest(b)));
   }
   
+  inline proc /(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to / have different sizes");
+
+    var result: a.size*(a(1) / b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) / b(d);
+
+    return result;
+  }
+  
   inline proc /(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to / have different sizes");
@@ -302,6 +388,17 @@
       return (a(1)/b(1), (...chpl__tupleRest(a)/chpl__tupleRest(b)));
   }
   
+  inline proc %(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to % have different sizes");
+
+    var result: a.size*(a(1) % b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) % b(d);
+
+    return result;
+  }
+  
   inline proc %(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to % have different sizes");
@@ -311,6 +408,17 @@
       return (a(1)%b(1), (...chpl__tupleRest(a)%chpl__tupleRest(b)));
   }
   
+  inline proc **(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to ** have different sizes");
+
+    var result: a.size*(a(1) ** b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) ** b(d);
+
+    return result;
+  }
+  
   inline proc **(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to ** have different sizes");
@@ -320,6 +428,17 @@
       return (a(1)**b(1), (...chpl__tupleRest(a)**chpl__tupleRest(b)));
   }
   
+  inline proc &(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to & have different sizes");
+
+    var result: a.size*(a(1) & b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) & b(d);
+
+    return result;
+  }
+  
   inline proc &(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to & have different sizes");
@@ -329,6 +448,17 @@
       return (a(1)&b(1), (...chpl__tupleRest(a)&chpl__tupleRest(b)));
   }
   
+  inline proc |(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to | have different sizes");
+
+    var result: a.size*(a(1) | b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) | b(d);
+
+    return result;
+  }
+  
   inline proc |(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to | have different sizes");
@@ -338,6 +468,17 @@
       return (a(1)|b(1), (...chpl__tupleRest(a)|chpl__tupleRest(b)));
   }
   
+  inline proc ^(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to ^ have different sizes");
+
+    var result: a.size*(a(1) ^ b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) ^ b(d);
+
+    return result;
+  }
+  
   inline proc ^(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to ^ have different sizes");
@@ -347,6 +488,17 @@
       return (a(1)^b(1), (...chpl__tupleRest(a)^chpl__tupleRest(b)));
   }
   
+  inline proc <<(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to << have different sizes");
+
+    var result: a.size*(a(1) << b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) << b(d);
+
+    return result;
+  }
+  
   inline proc <<(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to << have different sizes");
@@ -356,6 +508,17 @@
       return (a(1)<<b(1), (...chpl__tupleRest(a)<<chpl__tupleRest(b)));
   }
   
+  inline proc >>(a: _tuple, b: _tuple) where chpl_TwoHomogTuples(a,b) {
+    if a.size != b.size then
+      compilerError("tuple operands to >> have different sizes");
+
+    var result: a.size*(a(1) >> b(1)).type;
+    for param d in 1..a.size do
+      result(d) = a(d) >> b(d);
+
+    return result;
+  }
+  
   inline proc >>(a: _tuple, b: _tuple) {
     if a.size != b.size then
       compilerError("tuple operands to >> have different sizes");
@@ -434,168 +597,168 @@
   // standard overloaded binary operators on homog tuple / scalar pairs
   
   inline proc +(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)+y,);
-    else
-      return (x(1)+y, (...chpl__tupleRest(x)+y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) + y;
+    return result;
   }
 
   inline proc +(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x+y(1),);
-    else
-      return (x+y(1), (...x+chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x + y(d);
+    return result;
   }
 
   inline proc -(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)-y,);
-    else
-      return (x(1)-y, (...chpl__tupleRest(x)-y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) - y;
+    return result;
   }
 
   inline proc -(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x-y(1),);
-    else
-      return (x-y(1), (...x-chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x - y(d);
+    return result;
   }
 
   inline proc *(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)*y,);
-    else
-      return (x(1)*y, (...chpl__tupleRest(x)*y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) * y;
+    return result;
   }
 
   inline proc *(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        x: (y(1).type)  {
-    if y.size == 1 then
-      return (x*y(1),);
-    else
-      return (x*y(1), (...x*chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x * y(d);
+    return result;
   }
 
   inline proc /(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)/y,);
-    else
-      return (x(1)/y, (...chpl__tupleRest(x)/y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) / y;
+    return result;
   }
 
   inline proc /(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x/y(1),);
-    else
-      return (x/y(1), (...x/chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x / y(d);
+    return result;
   }
 
   inline proc %(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)%y,);
-    else
-      return (x(1)%y, (...chpl__tupleRest(x)%y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) % y;
+    return result;
   }
 
   inline proc %(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x%y(1),);
-    else
-      return (x%y(1), (...x%chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x % y(d);
+    return result;
   }
 
   inline proc **(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)**y,);
-    else
-      return (x(1)**y, (...chpl__tupleRest(x)**y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) ** y;
+    return result;
   }
 
   inline proc **(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x**y(1),);
-    else
-      return (x**y(1), (...x**chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x ** y(d);
+    return result;
   }
 
   inline proc &(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)&y,);
-    else
-      return (x(1)&y, (...chpl__tupleRest(x)&y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) & y;
+    return result;
   }
 
   inline proc &(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x&y(1),);
-    else
-      return (x&y(1), (...x&chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x & y(d);
+    return result;
   }
 
   inline proc |(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)|y,);
-    else
-      return (x(1)|y, (...chpl__tupleRest(x)|y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) | y;
+    return result;
   }
 
   inline proc |(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x|y(1),);
-    else
-      return (x|y(1), (...x|chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x | y(d);
+    return result;
   }
 
   inline proc ^(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)^y,);
-    else
-      return (x(1)^y, (...chpl__tupleRest(x)^y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) ^ y;
+    return result;
   }
 
   inline proc ^(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x^y(1),);
-    else
-      return (x^y(1), (...x^chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x ^ y(d);
+    return result;
   }
 
   inline proc <<(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)<<y,);
-    else
-      return (x(1)<<y, (...chpl__tupleRest(x)<<y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) << y;
+    return result;
   }
 
   inline proc <<(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x<<y(1),);
-    else
-      return (x<<y(1), (...x<<chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x << y(d);
+    return result;
   }
 
   inline proc >>(x: _tuple, y: x(1).type) where isHomogeneousTuple(x) {
-    if x.size == 1 then
-      return (x(1)>>y,);
-    else
-      return (x(1)>>y, (...chpl__tupleRest(x)>>y));
+    var result: x.size * x(1).type;
+    for param d in 1..x.size do
+      result(d) = x(d) >> y;
+    return result;
   }
 
   inline proc >>(x: ?t, y: _tuple) where isHomogeneousTuple(y) && 
                                        t: (y(1).type)  {
-    if y.size == 1 then
-      return (x>>y(1),);
-    else
-      return (x>>y(1), (...x>>chpl__tupleRest(y)));
+    var result: y.size * y(1).type;
+    for param d in 1..y.size do
+      result(d) = x >> y(d);
+    return result;
   }
 
 
------------------------------------------------------------------------------
Rapidly troubleshoot problems before they affect your business. Most IT 
organizations don't have a clear picture of how application performance 
affects their revenue. With AppDynamics, you get 100% visibility into your 
Java,.NET, & PHP application. Start your 15-day FREE TRIAL of AppDynamics Pro!
http://pubads.g.doubleclick.net/gampad/clk?id=84349831&iu=/4140/ostg.clktrk
_______________________________________________
Chapel-developers mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/chapel-developers

Reply via email to