Nikerabbit has uploaded a new change for review.
https://gerrit.wikimedia.org/r/85633
Change subject: PoC RadixTree storage for message index
......................................................................
PoC RadixTree storage for message index
Not quite as performant as I hoped. Using the 56k or so message keys:
* Takes 1.3 MiB when serialized out in custom binary format...
* ...down to 0.5 KiB if compressed
* Takes about 6s to create the tree
* Takes about 1s to serialize the tree
Not tested lookup yet for correctness or speed.
Change-Id: I302637530de60ad27ae41a032cbc7d863e2a8e1f
---
A RadixTree.php
M _autoload.php
2 files changed, 242 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Translate
refs/changes/33/85633/1
diff --git a/RadixTree.php b/RadixTree.php
new file mode 100644
index 0000000..e6183eb
--- /dev/null
+++ b/RadixTree.php
@@ -0,0 +1,239 @@
+<?php
+
+class Node {
+ var $key;
+ var $value;
+ var $children;
+
+ public function __construct( $key = '', $value = null ) {
+ $this->key = $key;
+ $this->value = $value;
+ $this->children = array();
+ }
+
+ public function getCPL( $key, $max ) {
+ $cpl = 0;
+ while ( $cpl < $max && $key[$cpl] === $this->key[$cpl] ) {
+ $cpl++;
+ }
+ return $cpl;
+ }
+}
+
+class RadixTree {
+ var $values = array();
+ var $vcount = 0;
+ var $root;
+
+ public function getValue( $string ) {
+ if ( isset( $this->values[$string] ) ) {
+ return $this->values[$string];
+ } else {
+ return $this->values[$string] = $this->vcount++;
+ }
+ }
+
+
+ public function __construct() {
+ $this->root = new Node();
+ }
+
+ public function insert( $key, $value, $node = null ) {
+ if ( $node === null ) {
+ $node = $this->root;
+ }
+
+ $klen = strlen( $key );
+ $nklen = strlen( $node->key );
+ $max = min( $klen, $nklen );
+
+ $cpl = $node->getCPL( $key, $max );
+ $new_text = substr( $key, $cpl );
+
+ if ( $node->key === '' || $cpl === 0 || ( $cpl < $klen && $cpl
>= $nklen ) ) {
+ /* Binary search version of insert, slower
+ $low = 0;
+ $high = count( $node->children ) -1;
+
+ while ( $low <= $high ) {
+ $mid = (int)( ( $low + $high ) / 2 );
+ $child = $node->children[$mid];
+
+ $a = $new_text[0];
+ $b = $child->key[0];
+
+ if ( $a === $b ) {
+ $this->insert( $new_text, $value,
$child );
+ return;
+ } elseif ( $a < $b ) {
+ $high = $mid - 1;
+ } else {
+ $low = $mid + 1;
+ }
+ }
+
+ $n = new Node( $new_text, $this->getValue( $value ) );
+ array_splice( $node->children, $low, 0, array( $n ) );*/
+
+ foreach ( $node->children as $child ) {
+ if ( $new_text[0] === $child->key[0] ) {
+ # function call is slow
+ #if ( strncmp( $new_text, $child->key, 1 ) ===
0 ) {
+ $this->insert( $new_text, $value,
$child );
+ return;
+ }
+ }
+
+ $n = new Node( $new_text, $this->getValue( $value ) );
+ $node->children[] = $n;
+
+ } elseif ( $cpl === $klen && $cpl === $nklen ) {
+ if ( $node->value !== null ) {
+ throw new MWException( 'uga' );
+ } else {
+ $node->value = $this->getValue( $value );
+ }
+ } elseif ( $cpl > 0 && $cpl < $nklen ) {
+ $n1 = new Node( substr( $node->key, $cpl ),
$node->value );
+ $n1->children = $node->children;
+
+ $node->key = substr( $key, 0, $cpl );
+ $node->value = null;
+ $node->children = array( $n1 );
+
+ if ( $cpl < $klen ) {
+ $n2 = new Node( $new_text, $this->getValue(
$value ) );
+ $node->children[] = $n2;
+ } else {
+ $node->value = $this->getValue( $value );
+ }
+ } else {
+ $n = new Node( substr( $node->key, $cpl ), $node->value
);
+ $n->children = $node->children;
+
+ $node->key = $key;
+ $node->value = $this->getValue( $value );
+ $node->children[] = $n;
+ }
+ }
+
+ public function serialize() {
+ $this->offsetMap = array();
+ $offset = 0;
+
+ $output = '';
+ foreach ( $this->values as $v => $index ) {
+ $len = strlen( $v );
+ $output .= pack( 'C', $len );
+
+ $this->offsetMap[$index] = $offset;
+
+ $output .= $v;
+
+ // One byte for the length
+ $offset += 1 + $len;
+ }
+
+ $output .= "\0\0\0\0";
+ $output .= $this->printNode( $this->root );
+
+ return $output;
+ }
+
+
+ protected function replaceKeys( Node $node ) {
+ $node->key = $this->getValue( $node->key );
+ foreach ( $node->children as $child ) {
+ $this->replaceKeys( $child );
+ }
+ }
+
+ protected function printNode( Node $node ) {
+ $value = $node->value !== null ? $this->offsetMap[$node->value]
: null;
+ $count = count( $node->children );
+ $klen = strlen( $node->key );
+
+ if ( $klen > 254 ) {
+ throw new MWException( "Too long key" );
+ }
+
+ if ( $count > 254 ) {
+ throw new MWException( "Too many child" );
+ }
+
+ $output = pack( 'LCC', $value, $count, $klen );
+ $output .= $node->key;
+
+ // Serialize children
+ $children = array();
+ foreach ( $node->children as $child ) {
+ $children[] = $this->printNode( $child );
+ }
+
+ // Print the subnode offsets
+ $offset = strlen( $output ) + $count;
+ foreach ( $children as $child ) {
+ $output .= pack( 'S', $offset );
+ $offset += strlen( $child );
+ }
+
+ // Print the subnodes
+ foreach ( $children as $child ) {
+ $output .= $child;
+ }
+
+ return $output;
+ }
+}
+
+class RadixTreeLookup {
+ var $tree;
+ var $root;
+
+ public function __construct( $tree ) {
+ $this->tree = $tree;
+ $this->root = strpos( $tree, "\0\0\0\0" ) + 4;
+ }
+
+ public function lookup( $lookup ) {
+ return $this->visitNode( $this->root, $lookup );
+ }
+
+ public function visitNode( $start, $lookup ) {
+ $headerLength = 6;
+
+ $header = substr( $this->tree, $start, $headerLength );
+ $info = unpack( 'Lvalue/Ccount/Cklen', $header );
+ $key = substr( $this->tree, $start + $headerLength,
$info['klen'] );
+
+ if ( $key === $lookup ) {
+ if ( $info['value'] === null ) {
+ return null;
+ }
+
+ $offset = unpack( 'Clength', substr( $this->tree,
$info['value'], 1 ) );
+ $value = substr( $this->tree, $info['value'] + 1,
$offset['length'] );
+ return $value;
+ }
+
+ if ( strncmp( $key, $lookup, $info['klen'] ) !== 0 ) {
+ return false;
+ }
+
+ $offset = $start + $headerLength + $info['klen'];
+
+ for ( $i = 0; $i < $info['count']; $i++ ) {
+ $a = unpack( 'Snodestart', substr( $this->tree, $offset
+ $i * 2, 2 ) );
+
+ $nodestart = $start + $a['nodestart'];
+ $value = $this->visitNode( $nodestart, substr( $lookup,
$info['klen'] ) );
+ if ( $value === false ) {
+ continue;
+ }
+
+ return $value;
+ }
+
+ return null;
+ }
+}
diff --git a/_autoload.php b/_autoload.php
index bd836bc..b54b4de 100644
--- a/_autoload.php
+++ b/_autoload.php
@@ -28,6 +28,9 @@
$wgAutoloadClasses['TranslateHooks'] = "$dir/TranslateHooks.php";
$wgAutoloadClasses['TranslateTasks'] = "$dir/TranslateTasks.php";
$wgAutoloadClasses['TranslateUtils'] = "$dir/TranslateUtils.php";
+
+$wgAutoloadClasses['RadixTree'] = "$dir/RadixTree.php";
+$wgAutoloadClasses['RadixTreeLookup'] = "$dir/RadixTree.php";
/**@}*/
/**
--
To view, visit https://gerrit.wikimedia.org/r/85633
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I302637530de60ad27ae41a032cbc7d863e2a8e1f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Translate
Gerrit-Branch: master
Gerrit-Owner: Nikerabbit <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits