Does it make sense to reuse output row in enumerators?

9 views
Skip to first unread message

Vladimir Sitnikov

unread,
Apr 21, 2014, 7:14:36 AM4/21/14
to opti...@googlegroups.com
Hi,

Currently Optiq creates new array/custom class for Enumerable.current() method:

            public Object current() {
              final Object[] current = (Object[]) inputEnumerator.current();
              return new Object[] {
                  net.hydromatic.optiq.runtime.SqlFunctions.toLong(current[4]) > 0L ? (Float) current[5] : (Float) null,
                  5,
                  (Float) current[6],
                  (Long) current[7],
                  (Long) current[8],
                  (Long) current[9],
                  (Long) current[10],
                  net.hydromatic.optiq.runtime.SqlFunctions.toInt(current[1]),
                  net.hydromatic.optiq.runtime.SqlFunctions.toInt(current[0])};
            }

I wonder if we can optimize it to reuse the same row holder, but filling it with new data during each iteration.

I guess the following should be better in terms of number of allocated and thrown away objects:
            final Object[] res = new Object[9];
            public Object current() {
              final Object[] current = (Object[]) inputEnumerator.current();
              res[0] = net.hydromatic.optiq.runtime.SqlFunctions.toLong(current[4]) > 0L ? (Float) current[5] : (Float) null;
              res[1] = 5;
              res[2] = (Float) current[6],
              res[3] = (Long) current[7],
              res[4] = (Long) current[8],
              res[5] = (Long) current[9],
              res[6] = (Long) current[10],
              res[7] = net.hydromatic.optiq.runtime.SqlFunctions.toInt(current[1]),
              res[8] = net.hydromatic.optiq.runtime.SqlFunctions.toInt(current[0])};
              return res;
            }

Julian Hyde

unread,
Apr 21, 2014, 3:07:21 PM4/21/14
to opti...@googlegroups.com
I’ve thought about this a lot. I think it would save modest amounts of GC load, but it would put a greater burden on the consumer (in fact all downstream consumers). That would cause bugs and more complex code-generation.

There are other ways to decrease GC load. Removing unnecessary boxing of field values (which you’ve mentioned in another thread). We should use a synthetic record rather than an array of objects wherever possible. And we should avoid generating bridge methods like this:

new Function2() {
public int compare(int i1, int i2) { return i1 - i2; }
public int compare(Integer o1, Integer o2) { return o1.intValue(), o2.intValue(); }
public int compare(Object o1, Object o2) { return compare((Integer) o1, (Integer) o2); }
}

(This is not the exact code we generate, but I saw something similar to this the other day.)

When we have dealt with the smaller boxing/unboxing issues, I think we should turn our attention to the bigger memory issue: to achieve good performance, we should not be using row-at-a-time iterators (or enumerators), and we should not be allocating records on the stack. Each operator should read a batch of records from a contiguous buffer (either an array or an off-heap byte buffer) and write a batch of records.

I’m not even sure that we (optiq) should be doing this, since Drill etc. already do this. They have a bunch of operators that work efficiently on in-memory data.

However I do think it’s worth investing, as you are, in how we do code-generation for scalar expressions, and in particular optimizing for null values and 3-value logic. The same code generation logic will be used for both row-at-a-time-on-heap and batch-at-a-time-off-heap processing.

Julian

Vladimir Sitnikov

unread,
Apr 21, 2014, 4:06:53 PM4/21/14
to opti...@googlegroups.com
I've made a quick proof of concept: https://github.com/vlsi/optiq/compare/reuse_output_tuple
It works as expected, however the problem is _materialization_ nodes: Enumerable.into tries to feed _the same_ Object[] into ArrayList, and fails drastically.
Will try overriding into(Collection) and see what happens then.

>>I’m not even sure that we (optiq) should be doing this, since Drill etc. already do this
Do you suggest using drill for Eclipse Memory Analyzer query language then?
Reply all
Reply to author
Forward
0 new messages