diff --git a/src/mol-data/_spec/equiv-index.spec.ts b/src/mol-data/_spec/equiv-index.spec.ts
index 9083c0145d0258be5815f9bc0c6f49c9effa5062..339022a969f1e072aeaa0199be98926106332623 100644
--- a/src/mol-data/_spec/equiv-index.spec.ts
+++ b/src/mol-data/_spec/equiv-index.spec.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import EquivalenceClasses from '../util/equivalence-classes'
+import { EquivalenceClasses } from '../util'
 
 describe('equiv-classes', () => {
     it('integer mod classes', () => {
diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts
index 54fe666de2589facbd59bc5107c4063a3d8cc194..adcd28cc2b69648eb4481a5478534052bf6b27b7 100644
--- a/src/mol-data/db/column.ts
+++ b/src/mol-data/db/column.ts
@@ -80,6 +80,10 @@ namespace Column {
         valueKind?: (row: number) => ValueKind,
     }
 
+    export function is(v: any): v is Column<any> {
+        return !!v && !!(v as Column<any>).schema && !!(v as Column<any>).value;
+    }
+
     export const enum ValueKind { Present = 0, NotPresent = 1, Unknown = 2 }
 
     export function Undefined<T extends Schema>(rowCount: number, schema: T): Column<T['T']> {
@@ -306,6 +310,7 @@ function mapToArrayImpl<T, S>(c: Column<T>, f: (v: T) => S, ctor: Column.ArrayCt
 }
 
 function areColumnsEqual(a: Column<any>, b: Column<any>) {
+    if (a === b) return true;
     if (a.rowCount !== b.rowCount || a.isDefined !== b.isDefined || a.schema.valueType !== b.schema.valueType) return false;
     if (!!a['@array'] && !!b['@array']) return areArraysEqual(a, b);
     return areValuesEqual(a, b);
diff --git a/src/mol-data/db/table.ts b/src/mol-data/db/table.ts
index d01629bf1650134e148ad469bcc31d37a4517cda..6335e37ed50eef0e761a90549ed53590e219647b 100644
--- a/src/mol-data/db/table.ts
+++ b/src/mol-data/db/table.ts
@@ -130,6 +130,26 @@ namespace Table {
 
         return true;
     }
+
+    /** Allocate a new object with the given row values. */
+    export function getRow<S extends Schema>(table: Table<S>, index: number) {
+        const row: Row<S> = Object.create(null);
+        const { _columns: cols } = table;
+        for (let i = 0; i < cols.length; i++) {
+            const c = cols[i];
+            row[c] = table[c].value(index);
+        }
+        return row;
+    }
+
+    export function getRows<S extends Schema>(table: Table<S>) {
+        const ret: Row<S>[] = [];
+        const { _rowCount: c } = table;
+        for (let i = 0; i < c; i++) {
+            ret[i] = getRow(table, i);
+        }
+        return ret;
+    }
 }
 
 export default Table
\ No newline at end of file
diff --git a/src/mol-data/int.ts b/src/mol-data/int.ts
index 54c2795bbc6f772ebd25ee58501119ded48b35fe..24b005bd24adbc9949770670c2c6fb2a4535485b 100644
--- a/src/mol-data/int.ts
+++ b/src/mol-data/int.ts
@@ -10,6 +10,7 @@ import Segmentation from './int/segmentation'
 import SortedArray from './int/sorted-array'
 import Tuple from './int/tuple'
 import LinkedIndex from './int/linked-index'
+import IntMap from './int/map'
 import Iterator from './iterator'
 
-export { Interval, OrderedSet, Segmentation, SortedArray, Tuple, LinkedIndex, Iterator }
\ No newline at end of file
+export { Interval, OrderedSet, Segmentation, SortedArray, Tuple, LinkedIndex, IntMap, Iterator }
\ No newline at end of file
diff --git a/src/mol-data/int/map.ts b/src/mol-data/int/map.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1ef86a532e7f382cd13c47fe9de7a93a1f151895
--- /dev/null
+++ b/src/mol-data/int/map.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { iterableToArray } from '../util'
+
+/** Immutable by convention IntMap */
+interface IntMap<T> {
+    keys(): IterableIterator<number>,
+    values(): IterableIterator<T>,
+    get(key: number): T,
+    readonly size: number
+}
+
+namespace IntMap {
+    export interface Mutable<T> extends IntMap<T> {
+        set(key: number, value: T): void;
+    }
+
+    export function keyArray<T>(map: IntMap<T>): number[] {
+        return iterableToArray(map.keys());
+    }
+
+    export function ofObj<T>(obj: { [key: number]: T }): IntMap<T> {
+        const keys = Object.keys(obj);
+        const ret = new Map<number, T>();
+        for (let i = 0, _i = keys.length; i < _i; i++) {
+            const k = keys[i];
+            ret.set(+k, obj[k as any]);
+        }
+        return ret as IntMap<T>;
+    }
+
+    export function Mutable<T>(): Mutable<T> {
+        return new Map<number, T>() as Mutable<T>;
+    }
+
+    export function asImmutable<T>(map: IntMap<T>): IntMap<T> {
+        return map;
+    }
+}
+
+export default IntMap;
\ No newline at end of file
diff --git a/src/mol-data/util.ts b/src/mol-data/util.ts
index ab0c73fcfc7be65d2974c403b7fc010dc118b95a..629b3b517d6f1ad80151a734f369083529c00ba5 100644
--- a/src/mol-data/util.ts
+++ b/src/mol-data/util.ts
@@ -4,12 +4,11 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import ChunkedArray from './util/chunked-array'
-import EquivalenceClasses from './util/chunked-array'
-import HashSet from './util/hash-set'
-import UniqueArray from './util/unique-array'
-
+export * from './util/chunked-array'
+export * from './util/unique-array'
+export * from './util/hash-set'
+export * from './util/equivalence-classes'
 export * from './util/hash-functions'
 export * from './util/sort'
-
-export { ChunkedArray, EquivalenceClasses, HashSet, UniqueArray }
\ No newline at end of file
+export * from './util/grouping'
+export * from './util/array'
\ No newline at end of file
diff --git a/src/mol-data/util/array.ts b/src/mol-data/util/array.ts
new file mode 100644
index 0000000000000000000000000000000000000000..664138327b5b67fab885efd6e0ec035f6efaf895
--- /dev/null
+++ b/src/mol-data/util/array.ts
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * from https://github.com/dsehnal/CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export function arrayFind<T>(array: ArrayLike<T>, f: (v: T) => boolean): T | undefined {
+    for (let i = 0, _i = array.length; i < _i; i++) {
+        if (f(array[i])) return array[i];
+    }
+    return void 0;
+}
+
+export function iterableToArray<T>(it: IterableIterator<T>): T[] {
+    if (Array.from) return Array.from(it);
+    
+    const ret = [];
+    while (true) {
+        const { done, value } = it.next();
+        if (done) break;
+        ret[ret.length] = value;
+    }
+    return ret;
+}
\ No newline at end of file
diff --git a/src/mol-data/util/chunked-array.ts b/src/mol-data/util/chunked-array.ts
index 0664a4c12ce99348f609b0d8558e28836f0b8dc2..f9b977571f543a317d0c12a618f61442af8fd3c3 100644
--- a/src/mol-data/util/chunked-array.ts
+++ b/src/mol-data/util/chunked-array.ts
@@ -134,4 +134,4 @@ namespace ChunkedArray {
     }
 }
 
-export default ChunkedArray
\ No newline at end of file
+export { ChunkedArray }
\ No newline at end of file
diff --git a/src/mol-data/util/equivalence-classes.ts b/src/mol-data/util/equivalence-classes.ts
index 936d39b299ed33956605642cd7b2c8a7041ef368..cc5f4af62e60d3bc252a4c254a0e67b01e19b383 100644
--- a/src/mol-data/util/equivalence-classes.ts
+++ b/src/mol-data/util/equivalence-classes.ts
@@ -41,8 +41,6 @@ class EquivalenceClassesImpl<K, V> {
     constructor(private getHash: (v: V) => any, private areEqual: (a: V, b: V) => boolean) { }
 }
 
-function EquivalenceClasses<K, V>(getHash: (x: V) => any, areEqual: (a: V, b: V) => boolean) {
+export function EquivalenceClasses<K, V>(getHash: (x: V) => any, areEqual: (a: V, b: V) => boolean) {
     return new EquivalenceClassesImpl<K, V>(getHash, areEqual);
 }
-
-export default EquivalenceClasses;
\ No newline at end of file
diff --git a/src/mol-data/util/grouping.ts b/src/mol-data/util/grouping.ts
index 213f3cf541e4ed9ae5e988b316be5c5423dcc490..71e9ce5a045ce14238ae3a1ffece9e08114a196b 100644
--- a/src/mol-data/util/grouping.ts
+++ b/src/mol-data/util/grouping.ts
@@ -4,31 +4,34 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import { Column } from '../db'
+
 export interface Grouping<V, K> {
+    map: Map<K, V[]>,
     keys: ReadonlyArray<K>,
     groups: ReadonlyArray<ReadonlyArray<V>>
 }
 
 class GroupingImpl<K, V> {
-    private byKey = new Map<K, V[]>();
+    readonly map = new Map<K, V[]>();
     readonly keys: K[] = [];
     readonly groups: V[][] = [];
 
     add(a: V) {
         const key = this.getKey(a) as any;
-        if (!!this.byKey.has(key)) {
-            const group = this.byKey.get(key)!;
+        if (!!this.map.has(key)) {
+            const group = this.map.get(key)!;
             group[group.length] = a;
         } else {
             const group = [a];
-            this.byKey.set(key, group);
+            this.map.set(key, group);
             this.keys[this.keys.length] = key;
             this.groups[this.groups.length] = group;
         }
     }
 
     getGrouping(): Grouping<V, K> {
-        return { keys: this.keys, groups: this.groups };
+        return { keys: this.keys, groups: this.groups, map: this.map };
     }
 
     constructor(private getKey: (v: V) => K) { }
@@ -38,10 +41,13 @@ export function Grouper<V, K>(getKey: (x: V) => K) {
     return new GroupingImpl<K, V>(getKey);
 }
 
-function groupBy<V, K>(values: ArrayLike<V>, getKey: (x: V) => K) {
+export function groupBy<V, K>(values: ArrayLike<V> | Column<V>, getKey: (x: V) => K) {
     const gs = Grouper(getKey);
-    for (let i = 0, _i = values.length; i < _i; i++) gs.add(values[i]);
+    if (Column.is(values)) {
+        const v = values.value;
+        for (let i = 0, _i = values.rowCount; i < _i; i++) gs.add(v(i));
+    } else {
+        for (let i = 0, _i = values.length; i < _i; i++) gs.add(values[i]);
+    }
     return gs.getGrouping();
-}
-
-export default groupBy;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/mol-data/util/hash-set.ts b/src/mol-data/util/hash-set.ts
index 297b8eeb6b9c09c121739a7c9a468d3443a343dc..0e41ad62f323deef6a486af6de38464f69919dbc 100644
--- a/src/mol-data/util/hash-set.ts
+++ b/src/mol-data/util/hash-set.ts
@@ -45,8 +45,6 @@ class HashSetImpl<T> implements SetLike<T> {
 }
 // TODO: add implementations with multilevel hashing support?
 
-function HashSet<T>(getHash: (v: T) => any, areEqual: (a: T, b: T) => boolean): SetLike<T> {
+export function HashSet<T>(getHash: (v: T) => any, areEqual: (a: T, b: T) => boolean): SetLike<T> {
     return new HashSetImpl<T>(getHash, areEqual);
-}
-
-export default HashSet;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/mol-data/util/unique-array.ts b/src/mol-data/util/unique-array.ts
index f628baa9601ff5d14c01a8af256b996fcf150786..5a5caa501af2ba0ba4098c9cf0904ee8d9296859 100644
--- a/src/mol-data/util/unique-array.ts
+++ b/src/mol-data/util/unique-array.ts
@@ -21,4 +21,4 @@ namespace UniqueArray {
     }
 }
 
-export default UniqueArray
\ No newline at end of file
+export { UniqueArray }
\ No newline at end of file
diff --git a/src/mol-io/common/binary-cif/array-encoder.ts b/src/mol-io/common/binary-cif/array-encoder.ts
index 344171b8a8a910f45d32d6c6d885ab9c46b6200f..67a9084ca0a578179bcf589a3e4a47907ed2e6ee 100644
--- a/src/mol-io/common/binary-cif/array-encoder.ts
+++ b/src/mol-io/common/binary-cif/array-encoder.ts
@@ -7,7 +7,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import ChunkedArray from 'mol-data/util/chunked-array'
+import { ChunkedArray } from 'mol-data/util'
 import { Encoding, EncodedData } from './encoding'
 
 export interface ArrayEncoder {
diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts
index 38303de0e1951c84397c3aad3621a8a6b4055730..dfe599c2a6aec7c4a32cc8c5499e0cb7112d8a18 100644
--- a/src/mol-model/structure/model/properties/symmetry.ts
+++ b/src/mol-model/structure/model/properties/symmetry.ts
@@ -5,6 +5,7 @@
  */
 
 import SymmetryOperator from 'mol-math/geometry/symmetry-operator'
+import { arrayFind } from 'mol-data/util'
 import { Query } from '../../query'
 import { Model } from '../../model'
 
@@ -47,12 +48,8 @@ namespace Symmetry {
     export const Empty: Symmetry = { assemblies: [] };
 
     export function findAssembly(model: Model, id: string): Assembly | undefined {
-        const { assemblies } = model.symmetry;
         const _id = id.toLocaleLowerCase();
-        for (let i = 0; i < assemblies.length; i++) {
-            if (assemblies[i].id.toLowerCase() === _id) return assemblies[i];
-        }
-        return void 0;
+        return arrayFind(model.symmetry.assemblies, a => a.id.toLowerCase() === _id);
     }
 }
 
diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts
index 8504e425237b3761fdb7c3704aef40f3b8fd1432..c1e0f9fb179d72184e14e492c440bf7d60e6c0b5 100644
--- a/src/mol-model/structure/query/selection.ts
+++ b/src/mol-model/structure/query/selection.ts
@@ -5,7 +5,7 @@
  */
 
 import Iterator from 'mol-data/iterator'
-import HashSet from 'mol-data/util/hash-set'
+import { HashSet } from 'mol-data/util'
 import { Structure, Atom, AtomSet } from '../structure'
 
 type Selection =
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index 317ea029f1593ae17ff62f7cc3e60222c786f7b5..8d5af1dcb383aafde5854ecbdaf787a01b6f734a 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -5,7 +5,7 @@
  */
 
 import { OrderedSet, Iterator } from 'mol-data/int'
-import UniqueArray from 'mol-data/util/unique-array'
+import { UniqueArray } from 'mol-data/util'
 import SymmetryOperator from 'mol-math/geometry/symmetry-operator'
 import { Model, Format } from '../model'
 import Unit from './unit'
diff --git a/src/perf-tests/chunked-array-vs-native.ts b/src/perf-tests/chunked-array-vs-native.ts
index 1b145425d76503a02dcd3e6ddad59d9496273707..e7843412a322591b87789205f0dbae2ce4de27dc 100644
--- a/src/perf-tests/chunked-array-vs-native.ts
+++ b/src/perf-tests/chunked-array-vs-native.ts
@@ -1,5 +1,5 @@
 import * as B from 'benchmark'
-import ChunkedArray from 'mol-data/util/chunked-array'
+import { ChunkedArray } from 'mol-data/util'
 
 function testNative(size: number) {
     const xs = new Array(size);
diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts
index 699c9037cafc14c7502954b09df33cee123854d4..07bb5919a28ed67f6b0427a68ba894c37ff3b5a1 100644
--- a/src/perf-tests/sets.ts
+++ b/src/perf-tests/sets.ts
@@ -331,7 +331,120 @@ export namespace ObjectVsMap {
     }
 }
 
-ObjectVsMap.run();
+export namespace IntVsStringIndices {
+    type WithKeys<K> = { keys: K[], data: { [key: number]: number } }
+    type MapWithKeys = { keys: number[], map: Map<number, number> }
+
+    function createCacheKeys(n: number): WithKeys<number> {
+        const data = Object.create(null), keys = [];
+        data.__ = void 0;
+        delete data.__;
+        for (let i = 1; i <= n; i++) {
+            const k = i * (i + 1);
+            keys[keys.length] = k;
+            data[k] = i + 1;
+        }
+        return { data, keys };
+    }
+
+    function createMapKeys(n: number): MapWithKeys {
+        const map = new Map<number, number>(), keys = [];
+        for (let i = 1; i <= n; i++) {
+            const k = i * (i + 1);
+            keys[keys.length] = k;
+            map.set(k, i + 1);
+        }
+        return { map, keys };
+    }
+
+    export function createInt(n: number) {
+        const ret = Object.create(null);
+        ret.__ = void 0;
+        delete ret.__;
+        for (let i = 1; i <= n; i++) ret[i * (i + 1)] = i + 1;
+        return ret;
+    }
+
+    export function createStr(n: number) {
+        const ret = Object.create(null);
+        ret.__ = void 0;
+        delete ret.__;
+        for (let i = 1; i <= n; i++) ret['' + (i * (i + 1))] = i + 1;
+        return ret;
+    }
+
+    export function createMap(n: number) {
+        const map = new Map<number, number>();
+        for (let i = 1; i <= n; i++) map.set(i * (i + 1), i + 1);
+        return map;
+    }
+
+    export function sumInt(xs: { [key: number]: number }) {
+        let s = 0;
+        const keys = Object.keys(xs);
+        for (let i = 0, _i = keys.length; i < _i; i++) s += xs[+keys[i]];
+        return s;
+    }
+
+    export function sumStr(xs: { [key: string]: number }) {
+        let s = 0;
+        const keys = Object.keys(xs);
+        for (let i = 0, _i = keys.length; i < _i; i++) s += xs[keys[i]];
+        return s;
+    }
+
+    export function sumMap(map: Map<number, number>) {
+        let s = 0;
+        const values = map.keys();
+        while (true) {
+            const { done, value } = values.next();
+            if (done) break;
+            s += value;
+        }
+        return s;
+    }
+
+    export function sumCached(xs: WithKeys<number>) {
+        let s = 0;
+        const keys = xs.keys, data = xs.data;
+        for (let i = 0, _i = keys.length; i < _i; i++) s += data[keys[i]];
+        return s;
+    }
+
+    export function sumKeyMap(xs: MapWithKeys) {
+        let s = 0;
+        const keys = xs.keys, map = xs.map;
+        for (let i = 0, _i = keys.length; i < _i; i++) s += map.get(keys[i])!;
+        return s;
+    }
+
+    export function run() {
+        const N = 1000;
+        //const int = createInt(N);
+        const map = createMap(N);
+        //const str = createStr(N);
+        const keys = createCacheKeys(N);
+        const keyMap = createMapKeys(N);
+        console.log(sumMap(map), sumCached(keys), sumKeyMap(keyMap));
+        new B.Suite()
+            //.add('c int', () => createInt(N))
+            .add('q map', () => sumMap(map))
+            .add('c map', () => createMap(N))
+            .add('c mk', () => createMapKeys(N))
+            //.add('c str', () => createStr(N))
+            .add('c cc', () => createCacheKeys(N))
+            //.add('q int', () => sumInt(int))
+            .add('q mk', () => sumKeyMap(keyMap))
+            //.add('q str', () => sumStr(str))
+            .add('q cc', () => sumCached(keys))
+            .on('cycle', (e: any) => console.log(String(e.target)))
+            .run();
+    }
+}
+
+IntVsStringIndices.run();
+
+//ObjectVsMap.run();
 
 //testSegments();