Skip to content

Commit

Permalink
Reduce the number of index array computations
Browse files Browse the repository at this point in the history
  • Loading branch information
mattpap committed Nov 30, 2023
1 parent 036345e commit eb100b8
Show file tree
Hide file tree
Showing 18 changed files with 138 additions and 73 deletions.
8 changes: 6 additions & 2 deletions bokehjs/src/lib/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,9 @@ export type Interval = {
end: number
}

export {BitSet as Indices} from "./util/bitset"
export type {RaggedArray} from "./util/ragged_array"
export type Indices = {
[Symbol.iterator](): Iterator<number>
select<T>(array: Arrayable<T>): Arrayable<T>
readonly count: number
readonly size: number
}
5 changes: 1 addition & 4 deletions bokehjs/src/lib/core/util/arrayable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ export function is_sorted<T>(array: Arrayable<T>): boolean {
}

export function copy<T>(array: Arrayable<T>): Arrayable<T> {
if (Array.isArray(array))
return array.slice()
else
return new (array.constructor as any)(array)
return array.slice()
}

export function splice<T>(array: Arrayable<T>, start: number, k?: number, ...items: T[]): Arrayable<T> {
Expand Down
14 changes: 9 additions & 5 deletions bokehjs/src/lib/core/util/bitset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,16 @@ export class BitSet implements Equatable {
select<T>(array: Arrayable<T>): Arrayable<T> {
this._check_array_length(array)
const n = this.count
const result = new (array.constructor as ArrayableNew)<T>(n)
let i = 0
for (const j of this) {
result[i++] = array[j]
if (n == this.size) {
return array.slice()
} else {
const result = new (array.constructor as ArrayableNew)<T>(n)
let i = 0
for (const j of this) {
result[i++] = array[j]
}
return result
}
return result
}

protected _check_bounds(k: number): void {
Expand Down
58 changes: 58 additions & 0 deletions bokehjs/src/lib/core/util/indices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type {Arrayable, ArrayableNew, Indices} from "../types"
import {AssertionError} from "./assert"
import {BitSet} from "./bitset"
import {range} from "./array"

export const PackedIndices = BitSet
export type PackedIndices = BitSet

export class OpaqueIndices implements Indices {

protected readonly _array: number[]

constructor(readonly size: number, init: Iterable<number> | 1 | 0 = 0) {
if (init == 0) {
this._array = []
} else if (init == 1) {
this._array = range(0, size)
} else {
this._array = [...init]
}
this.count = this._array.length
}

readonly count: number

get array(): number[] { // TODO readonly
return this._array
}

static from_packed(indices: PackedIndices): OpaqueIndices {
return new OpaqueIndices(indices.size, indices)
}

*[Symbol.iterator](): Iterator<number> {
yield* this._array
}

select<T>(array: Arrayable<T>): Arrayable<T> {
this._check_array_length(array)
const n = this.count
if (n == this.size) {
return array.slice()
} else {
const result = new (array.constructor as ArrayableNew)<T>(n)
let i = 0
for (const j of this._array) {
result[i++] = array[j]
}
return result
}
}

protected _check_array_length(other: Arrayable) {
if (!(this.size <= other.length)) {
throw new AssertionError(`Size mismatch (${this.size} != ${other.length})`)
}
}
}
6 changes: 3 additions & 3 deletions bokehjs/src/lib/core/util/spatial.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import FlatBush from "flatbush"

import type {Rect, TypedArray} from "../types"
import {Indices} from "../types"
import {PackedIndices} from "./indices"
import {empty} from "./bbox"

function upperBound(value: number, arr: ArrayLike<number>): number {
Expand All @@ -24,14 +24,14 @@ class _FlatBush extends FlatBush {
return this._boxes
}

search_indices(minX: number, minY: number, maxX: number, maxY: number): Indices {
search_indices(minX: number, minY: number, maxX: number, maxY: number): PackedIndices {
if (this._pos !== this._boxes.length) {
throw new Error("Data not yet indexed - call index.finish().")
}

let nodeIndex: number | undefined = this._boxes.length - 4
const queue = []
const results = new Indices(this.numItems)
const results = new PackedIndices(this.numItems)

while (nodeIndex !== undefined) {
// find the end index of the node
Expand Down
6 changes: 3 additions & 3 deletions bokehjs/src/lib/models/axes/axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {SerializableState} from "core/view"
import type {HAlign, VAlign} from "core/enums"
import {Align, Face, LabelOrientation} from "core/enums"
import type {Size, Layoutable} from "core/layout"
import {Indices} from "core/types"
import {PackedIndices} from "core/util/indices"
import type {Orient, Normal, Dimension} from "core/layout/side_panel"
import {Panel, SideLayout} from "core/layout/side_panel"
import type {Context2d} from "core/util/canvas"
Expand Down Expand Up @@ -388,7 +388,7 @@ export class AxisView extends GuideRendererView {
}

const n = labels.length
const indices = Indices.all_set(n)
const indices = PackedIndices.all_set(n)

const {items} = labels
const bboxes = items.map((l) => l.bbox())
Expand All @@ -406,7 +406,7 @@ export class AxisView extends GuideRendererView {
const {major_label_policy} = this.model
const selected = major_label_policy.filter(indices, bboxes, dist)

const ids = [...selected.ones()]
const ids = [...selected]
if (ids.length != 0) {
const cbox = this.canvas.bbox

Expand Down
6 changes: 3 additions & 3 deletions bokehjs/src/lib/models/filters/all_indices.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Filter} from "./filter"
import type * as p from "core/properties"
import {Indices} from "core/types"
import {PackedIndices} from "core/util/indices"
import type {ColumnarDataSource} from "../sources/columnar_data_source"

export namespace AllIndices {
Expand All @@ -18,8 +18,8 @@ export class AllIndices extends Filter {
super(attrs)
}

compute_indices(source: ColumnarDataSource): Indices {
compute_indices(source: ColumnarDataSource): PackedIndices {
const size = source.get_length() ?? 1
return Indices.all_set(size)
return PackedIndices.all_set(size)
}
}
8 changes: 4 additions & 4 deletions bokehjs/src/lib/models/filters/boolean_filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Filter} from "./filter"
import type * as p from "core/properties"
import {Indices} from "core/types"
import {PackedIndices} from "core/util/indices"
import type {ColumnarDataSource} from "../sources/columnar_data_source"

export namespace BooleanFilter {
Expand All @@ -26,13 +26,13 @@ export class BooleanFilter extends Filter {
}))
}

compute_indices(source: ColumnarDataSource): Indices {
compute_indices(source: ColumnarDataSource): PackedIndices {
const size = source.get_length() ?? 1
const {booleans} = this
if (booleans == null) {
return Indices.all_set(size)
return PackedIndices.all_set(size)
} else {
return Indices.from_booleans(size, booleans)
return PackedIndices.from_booleans(size, booleans)
}
}
}
8 changes: 4 additions & 4 deletions bokehjs/src/lib/models/filters/composite_filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Filter} from "./filter"
import type * as p from "core/properties"
import {Indices} from "core/types"
import {PackedIndices} from "core/util/indices"
import type {ColumnarDataSource} from "../sources/columnar_data_source"

export namespace CompositeFilter {
Expand Down Expand Up @@ -59,11 +59,11 @@ export abstract class CompositeFilter extends Filter {
})
}

compute_indices(source: ColumnarDataSource): Indices {
compute_indices(source: ColumnarDataSource): PackedIndices {
const {operands} = this
if (operands.length == 0) {
const size = source.get_length() ?? 1
return Indices.all_set(size)
return PackedIndices.all_set(size)
} else {
const [index, ...rest] = operands.map((op) => op.compute_indices(source))
for (const op of rest) {
Expand All @@ -73,5 +73,5 @@ export abstract class CompositeFilter extends Filter {
}
}

protected abstract _inplace_op(index: Indices, op: Indices): void
protected abstract _inplace_op(index: PackedIndices, op: PackedIndices): void
}
10 changes: 5 additions & 5 deletions bokehjs/src/lib/models/filters/customjs_filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Filter} from "./filter"
import type * as p from "core/properties"
import {Indices} from "core/types"
import {PackedIndices} from "core/util/indices"
import {keys, values} from "core/util/object"
import {isArrayOf, isBoolean, isInteger} from "core/util/types"
import type {ColumnarDataSource} from "../sources/columnar_data_source"
Expand Down Expand Up @@ -44,15 +44,15 @@ export class CustomJSFilter extends Filter {
return new Function(...this.names, "source", code)
}

compute_indices(source: ColumnarDataSource): Indices {
compute_indices(source: ColumnarDataSource): PackedIndices {
const size = source.get_length() ?? 1
const filter = this.func(...this.values, source)
if (filter == null) {
return Indices.all_set(size)
return PackedIndices.all_set(size)
} else if (isArrayOf(filter, isInteger)) {
return Indices.from_indices(size, filter)
return PackedIndices.from_indices(size, filter)
} else if (isArrayOf(filter, isBoolean)) {
return Indices.from_booleans(size, filter)
return PackedIndices.from_booleans(size, filter)
} else {
throw new Error(`expect an array of integers or booleans, or null, got ${filter}`)
}
Expand Down
4 changes: 2 additions & 2 deletions bokehjs/src/lib/models/filters/filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Model} from "../../model"
import type {DataSource} from "../sources/data_source"
import type {Indices} from "core/types"
import type {PackedIndices} from "core/util/indices"
import type * as p from "core/properties"

export namespace Filter {
Expand All @@ -18,5 +18,5 @@ export abstract class Filter extends Model {
super(attrs)
}

abstract compute_indices(source: DataSource): Indices
abstract compute_indices(source: DataSource): PackedIndices
}
8 changes: 4 additions & 4 deletions bokehjs/src/lib/models/filters/group_filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Filter} from "./filter"
import type * as p from "core/properties"
import {Indices} from "core/types"
import {PackedIndices} from "core/util/indices"
import {logger} from "core/logging"
import type {ColumnarDataSource} from "../sources/columnar_data_source"

Expand Down Expand Up @@ -29,14 +29,14 @@ export class GroupFilter extends Filter {
}))
}

compute_indices(source: ColumnarDataSource): Indices {
compute_indices(source: ColumnarDataSource): PackedIndices {
const column = source.get_column(this.column_name)
const size = source.get_length() ?? 1
if (column == null) {
logger.warn(`${this}: groupby column '${this.column_name}' not found in the data source`)
return Indices.all_set(size)
return PackedIndices.all_set(size)
} else {
const indices = new Indices(size, 0)
const indices = new PackedIndices(size, 0)
for (let i = 0; i < indices.size; i++) {
if (column[i] === this.group) {
indices.set(i)
Expand Down
8 changes: 4 additions & 4 deletions bokehjs/src/lib/models/filters/index_filter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Filter} from "./filter"
import type * as p from "core/properties"
import type {Arrayable} from "core/types"
import {Indices} from "core/types"
import {PackedIndices} from "core/util/indices"
import type {ColumnarDataSource} from "../sources/columnar_data_source"

export namespace IndexFilter {
Expand All @@ -27,13 +27,13 @@ export class IndexFilter extends Filter {
}))
}

compute_indices(source: ColumnarDataSource): Indices {
compute_indices(source: ColumnarDataSource): PackedIndices {
const size = source.get_length() ?? 1
const {indices} = this
if (indices == null) {
return Indices.all_set(size)
return PackedIndices.all_set(size)
} else {
return Indices.from_indices(size, indices)
return PackedIndices.from_indices(size, indices)
}
}
}
4 changes: 2 additions & 2 deletions bokehjs/src/lib/models/filters/inversion_filter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Filter} from "./filter"
import type * as p from "core/properties"
import type {Indices} from "core/types"
import type {PackedIndices} from "core/util/indices"
import type {ColumnarDataSource} from "../sources/columnar_data_source"

export namespace InversionFilter {
Expand Down Expand Up @@ -59,7 +59,7 @@ export class InversionFilter extends Filter {
})
}

compute_indices(source: ColumnarDataSource): Indices {
compute_indices(source: ColumnarDataSource): PackedIndices {
const index = this.operand.compute_indices(source)
index.invert()
return index
Expand Down
16 changes: 9 additions & 7 deletions bokehjs/src/lib/models/glyphs/glyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import type {Anchor} from "core/enums"
import type {ViewStorage, IterViews} from "core/build_views"
import {build_views} from "core/build_views"
import {logger} from "core/logging"
import type {Arrayable, Rect, FloatArray} from "core/types"
import {ScreenArray, Indices} from "core/types"
import type {Arrayable, Rect, FloatArray, Indices} from "core/types"
import {ScreenArray} from "core/types"
import {PackedIndices} from "core/util/indices"
import {isString} from "core/util/types"
import {RaggedArray} from "core/util/ragged_array"
import {inplace_map, max} from "core/util/arrayable"
Expand Down Expand Up @@ -365,15 +366,16 @@ export abstract class GlyphView extends View {
this._index = index
}

mask_data(): Indices {
mask_data(): PackedIndices {
/** Returns subset indices in the viewport. */
if (this._mask_data == null)
return Indices.all_set(this.data_size)
else
if (this._mask_data == null) {
return PackedIndices.all_set(this.data_size)
} else {
return this._mask_data()
}
}

protected _mask_data?(): Indices
protected _mask_data?(): PackedIndices

map_data(): void {
const self = this as any
Expand Down

0 comments on commit eb100b8

Please sign in to comment.