Skip to content

Commit

Permalink
Amortize quadratic execution time of traversal eclipse-platform#882
Browse files Browse the repository at this point in the history
TreeItem.getItem(int) had linear complexity, full traversal had
quadratic complexity. To alleviate this, we cache the result.

Note: this only speeds up breadth-first traversal as cache operates on "item of interest" basis, dropping contents if another item is requested.
  • Loading branch information
basilevs committed Nov 12, 2023
1 parent 999ba74 commit dc47830
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ public class Tree extends Composite {

private long headerCSSProvider;

private TreeItemCache itemCache = null;

static final int ID_COLUMN = 0;
static final int CHECKED_COLUMN = 1;
static final int GRAYED_COLUMN = 2;
Expand Down Expand Up @@ -4346,4 +4348,11 @@ public void dispose() {
headerCSSProvider = 0;
}
}

TreeItemCache getItemCache(TreeItem treeItem) {
if (itemCache == null || itemCache.owner != treeItem) {
itemCache = new TreeItemCache(treeItem);
}
return itemCache;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public class TreeItem extends Item {
Font[] cellFont;
String [] strings;
boolean cached, grayed, isExpanded, updated, settingData;
int itemCountCache = -1;
static final int EXPANDER_EXTRA_PADDING = 4;

/**
Expand Down Expand Up @@ -167,7 +166,9 @@ public TreeItem (TreeItem parentItem, int style, int index) {
this.parent = parent;
if (create) {
parent.createItem (this, parentIter, index);
resetParentCache();
if (parentIter != 0) {
parent._getItem(parentIter).resetCache();
}
} else {
handle = OS.g_malloc (GTK.GtkTreeIter_sizeof ());
GTK.gtk_tree_model_iter_nth_child (parent.modelHandle, handle, parentIter, index);
Expand Down Expand Up @@ -770,7 +771,7 @@ Rectangle getImageBoundsInPixels (int index) {
public int getItemCount () {
checkWidget();
if (!parent.checkData (this)) error (SWT.ERROR_WIDGET_DISPOSED);
return getItemCountImpl();
return getCache().getItemCount();
}

/**
Expand All @@ -793,9 +794,12 @@ public int getItemCount () {
public TreeItem getItem (int index) {
checkWidget();
if (index < 0) error (SWT.ERROR_INVALID_RANGE);
int itemCount = getItemCountImpl();
if (index >= itemCount) error (SWT.ERROR_INVALID_RANGE);
return parent._getItem (handle, index);
// It may seem that requesting GTK for an item by index is quicker,
// but it has O(N) execution time and cache of all children amortizes that.
// This avoids quadratic execution time on traversals
TreeItem[] items = getCache().getItems();
if (index >= items.length) error (SWT.ERROR_INVALID_RANGE);
return items[index];
}

/**
Expand Down Expand Up @@ -1100,7 +1104,7 @@ public void dispose () {
*/
public void removeAll () {
checkWidget ();
itemCountCache = 0;
resetCache();
long modelHandle = parent.modelHandle;
int length = GTK.gtk_tree_model_iter_n_children (modelHandle, handle);
if (length == 0) return;
Expand Down Expand Up @@ -1631,7 +1635,7 @@ public void setImage (Image [] images) {
public void setItemCount (int count) {
checkWidget ();
count = Math.max (0, count);
itemCountCache = count;
resetCache();
parent.setItemCount (handle, count);
}

Expand Down Expand Up @@ -1712,18 +1716,13 @@ public void setText (String [] strings) {
}
}

private void resetParentCache() {
TreeItem parentItem = getParentItem();
if (parentItem != null) {
parentItem.itemCountCache = -1;
}
private void resetCache() {
getCache().reset();
}

private int getItemCountImpl() {
if (itemCountCache < 0) {
itemCountCache = GTK.gtk_tree_model_iter_n_children (parent.modelHandle, handle);
}
return itemCountCache;
private TreeItemCache getCache() {
return parent.getItemCache(this);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (c) 2023, 2023 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Vasili Gulevich - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.widgets;

import java.util.*;

import org.eclipse.swt.internal.gtk.*;

/** Volatile information about a TreeItem, that takes long to compute
* @since 3.125**/
class TreeItemCache {
final TreeItem owner;
private TreeItem[] children;
private int itemCount = -1;

TreeItemCache(TreeItem owner) {
this.owner = Objects.requireNonNull(owner);
}

int getItemCount() {
if (itemCount < 0) {
itemCount = GTK.gtk_tree_model_iter_n_children (owner.parent.modelHandle, owner.handle);
}
return itemCount;
}

TreeItem[] getItems() {
if (children == null) {
children = owner.parent.getItems(owner.handle);
itemCount = children.length;
}
return children;
}

public void reset() {
itemCount = -1;
children = null;
}
}

0 comments on commit dc47830

Please sign in to comment.