Columnar Engine & Memory Layout¶
Traditional Java DataFrame implementations typically represent tables as List<Map<String, Object>> or arrays of boxed objects (Object[]). While flexible, this approach incurs substantial overhead:
1. Per-cell heap allocation: Every single primitive (int, double, boolean) is boxed into an Integer, Double, etc.
2. Poor cache locality: Row objects are scattered unpredictably across the JVM heap.
3. High garbage collection pressure: Hundreds of thousands of short-lived objects.
CupOfTea uses a dedicated Columnar Engine (ColumnVector) architecture.
🏗️ Architecture¶
classDiagram
class ColumnVector {
<<abstract>>
+int size()
+boolean isNull(int row)
+void setNull(int row)
+Object get(int row)
}
class IntColumnVector {
-int[] data
-BitSet nulls
+int getInt(int row)
}
class DoubleColumnVector {
-double[] data
-BitSet nulls
+double getDouble(int row)
}
class LongColumnVector {
-long[] data
-BitSet nulls
+long getLong(int row)
}
class FloatColumnVector {
-float[] data
-BitSet nulls
+float getFloat(int row)
}
class BooleanColumnVector {
-boolean[] data
-BitSet nulls
+boolean getBoolean(int row)
}
class StringColumnVector {
-byte[] values
-int[] offsets
-BitSet nulls
+String getString(int row)
+int hashRow(int row)
+boolean rowsEqual(int rowA, int rowB)
}
class ObjectColumnVector {
-Object[] data
-BitSet nulls
}
ColumnVector <|-- IntColumnVector
ColumnVector <|-- DoubleColumnVector
ColumnVector <|-- LongColumnVector
ColumnVector <|-- FloatColumnVector
ColumnVector <|-- BooleanColumnVector
ColumnVector <|-- StringColumnVector
ColumnVector <|-- ObjectColumnVector
🚀 Key Advantages¶
1. Primitive Contiguous Storage¶
- Data is stored in typed Java primitive arrays (
int[],double[],long[],float[],boolean[]). - Reads and iterations operate directly on continuous memory segments, maximizing L1/L2 CPU cache prefetching.
2. Space-Efficient Null Bitmasks¶
- Missing values are tracked using a compact
java.util.BitSet. - Rather than setting object pointers to
null, a single bit per row indicates whether a value is present or absent. - 1 million rows require only ~122 KB of null bitmask memory per column.
3. Minimized GC Pressure¶
- Primitive arrays are allocated in chunks. Transforming or filtering columnar vectors creates far fewer object allocations, dramatically easing JVM GC pauses on large datasets.
4. Zero-Allocation GroupBy Engine¶
- Traditional dataframe grouping creates millions of heap objects (
Map<List<Object>, List<Integer>>). - CupOfTea utilizes an open-addressing primitive hash table (
GroupHashTable) storing representative row indices in contiguousint[]arrays. - Aggregate reductions (
sum,mean,min,max,count) accumulate single-pass updates directly into primitive arrays (double[],long[],int[]) and populate destinationColumnVectorinstances without boxing or creating row-level objects.
⚡ Direct Vector Access¶
The engine exposes typed vector methods directly to eliminate auto-unboxing in hot paths: