Skip to content

Commit 1973556

Browse files
authored
Merge pull request #124 from SemanticMediaWiki/audit/perf-hot-path
Simplify Lua bridge hot path
2 parents 53c4476 + 8dedba0 commit 1973556

3 files changed

Lines changed: 121 additions & 37 deletions

File tree

src/LuaAskResultProcessor.php

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,35 @@
2222
class LuaAskResultProcessor {
2323

2424
/**
25-
* Holds all possible representations of "true" in this smw instance
26-
*
27-
* @var array
25+
* Cached representations of "true" — shared across all processor instances
26+
* within a request because the message bundle is request-stable.
2827
*/
29-
private $msgTrue;
28+
private static ?array $msgTrue = null;
3029

3130
/**
32-
* This counter serves as fallback, if no label for printout is specified
33-
*
34-
* @var int
31+
* Fallback index for printouts without a label.
3532
*/
36-
private $numericIndex;
33+
private int $numericIndex;
3734

38-
/**
39-
* Holds the query result for this object
40-
*
41-
* @var QueryResult
42-
*/
43-
private $queryResult;
35+
private QueryResult $queryResult;
36+
37+
private readonly Linker $linker;
4438

4539
/**
46-
* LuaAskResultProcessor constructor.
40+
* Memoised PrintRequest labels, keyed by spl_object_id.
41+
* Per-processor (i.e. per-ask-call) scope.
4742
*
48-
* @param QueryResult $queryResult
43+
* @var array<int, int|string>
4944
*/
45+
private array $printRequestLabels = [];
46+
5047
public function __construct( QueryResult $queryResult ) {
5148
$this->queryResult = $queryResult;
52-
$this->msgTrue = explode( ',', wfMessage( 'smw_true_words' )->text() . ',true,t,yes,y' );
49+
if ( self::$msgTrue === null ) {
50+
self::$msgTrue = explode( ',', wfMessage( 'smw_true_words' )->text() . ',true,t,yes,y' );
51+
}
5352
$this->numericIndex = 1;
53+
$this->linker = new Linker();
5454
}
5555

5656
/**
@@ -138,11 +138,15 @@ public function getDataFromResultArray( ResultArray $resultArray ) {
138138
* @return int|string
139139
*/
140140
public function getKeyFromPrintRequest( PrintRequest $printRequest ) {
141-
if ( $printRequest->getLabel() !== '' ) {
142-
return $printRequest->getText( SMW_OUTPUT_WIKI );
141+
$id = spl_object_id( $printRequest );
142+
if ( isset( $this->printRequestLabels[$id] ) ) {
143+
return $this->printRequestLabels[$id];
143144
}
144-
145-
return $this->getNumericIndex();
145+
$label = $printRequest->getLabel() !== ''
146+
? $printRequest->getText( SMW_OUTPUT_WIKI )
147+
: $this->getNumericIndex();
148+
$this->printRequestLabels[$id] = $label;
149+
return $label;
146150
}
147151

148152
/**
@@ -157,7 +161,7 @@ public function getValueFromDataValue( DataValue $dataValue ) {
157161
switch ( $dataValue->getTypeID() ) {
158162
case '_boo':
159163
// boolean value found, convert it
160-
$value = in_array( strtolower( $dataValue->getWikiValue() ?? 'null' ), $this->msgTrue );
164+
$value = in_array( strtolower( $dataValue->getWikiValue() ?? 'null' ), self::$msgTrue );
161165
break;
162166
case '_num':
163167
// number value found
@@ -167,7 +171,7 @@ public function getValueFromDataValue( DataValue $dataValue ) {
167171
break;
168172
default:
169173
# FIXME ignores parameter link=none|subject
170-
$value = $dataValue->getShortText( SMW_OUTPUT_WIKI, new Linker() );
174+
$value = $dataValue->getShortText( SMW_OUTPUT_WIKI, $this->linker );
171175
}
172176

173177
return $value;

src/ScribuntoLuaLibrary.php

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public function getQueryResult( $arguments = null ) {
130130
if ( !empty( $result["results"] ) ) {
131131
// as of now, "results" has page names as keys. lua is not very good, keeping non-number keys in order
132132
// so replace string keys with the corresponding number, starting with 0.
133-
$result["results"] = array_combine( range( 0, count( $result["results"] ) - 1 ), array_values( $result["results"] ) );
133+
$result["results"] = array_values( $result["results"] );
134134
}
135135

136136
return [ $this->convertArrayToLuaTable( $result ) ];
@@ -254,21 +254,26 @@ public function subobject( $arguments, $subobjectId = null ) {
254254
}
255255

256256
/**
257-
* This takes an array and converts it so, that the result is a viable lua table.
258-
* I.e. the resulting table has its numerical indices start with 1
259-
* If `$ar` is not an array, it is simply returned
260-
* @param mixed $ar
261-
* @return mixed array
257+
* Converts an array into a shape suitable for a Lua table: integer keys shift
258+
* by +1 (Lua arrays are 1-indexed), string keys are left untouched. Recurses
259+
* into nested arrays. Non-array inputs are returned unchanged.
260+
*
261+
* Precondition: integer-keyed inputs are expected to use contiguous 0-based
262+
* indices (as produced by `array_values()` or `QueryResultSerializer` output).
263+
* Sparse integer keys are shifted by +1 here rather than re-indexed to a
264+
* contiguous sequence — diverging from the pre-7.0 `array_unshift` + `unset`
265+
* implementation. No caller in this codebase feeds sparse keys.
262266
*/
263267
private function convertArrayToLuaTable( $ar ) {
264-
if ( is_array( $ar ) ) {
265-
foreach ( $ar as $key => $value ) {
266-
$ar[$key] = $this->convertArrayToLuaTable( $value );
267-
}
268-
array_unshift( $ar, '' );
269-
unset( $ar[0] );
268+
if ( !is_array( $ar ) ) {
269+
return $ar;
270+
}
271+
$result = [];
272+
foreach ( $ar as $key => $value ) {
273+
$newKey = is_int( $key ) ? $key + 1 : $key;
274+
$result[$newKey] = $this->convertArrayToLuaTable( $value );
270275
}
271-
return $ar;
276+
return $result;
272277
}
273278

274279
/**
@@ -307,7 +312,7 @@ private function doPostProcessParserFunctionCallResult( $parserFunctionResult )
307312
* @return LibraryFactory
308313
*/
309314
private function getLibraryFactory(): LibraryFactory {
310-
if ( !empty( $this->libraryFactory ) ) {
315+
if ( isset( $this->libraryFactory ) ) {
311316
return $this->libraryFactory;
312317
}
313318

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace SMW\Scribunto\Tests\Unit;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use ReflectionClass;
7+
use ReflectionMethod;
8+
use SMW\Scribunto\ScribuntoLuaLibrary;
9+
10+
/**
11+
* Characterisation test pinning down the current behaviour of the private
12+
* {@see ScribuntoLuaLibrary::convertArrayToLuaTable} helper before it is
13+
* refactored. The test invokes the private method via reflection so the
14+
* production class needs no visibility changes.
15+
*
16+
* @covers \SMW\Scribunto\ScribuntoLuaLibrary::convertArrayToLuaTable
17+
* @group semantic-scribunto
18+
*
19+
* @license GPL-2.0-or-later
20+
* @since 7.0.0
21+
*/
22+
class ConvertArrayToLuaTableTest extends TestCase {
23+
24+
private function invoke( mixed $input ): mixed {
25+
$instance = ( new ReflectionClass( ScribuntoLuaLibrary::class ) )
26+
->newInstanceWithoutConstructor();
27+
$method = new ReflectionMethod( ScribuntoLuaLibrary::class, 'convertArrayToLuaTable' );
28+
$method->setAccessible( true );
29+
return $method->invoke( $instance, $input );
30+
}
31+
32+
public function testNonArrayPassthrough(): void {
33+
$this->assertSame( 'hello', $this->invoke( 'hello' ) );
34+
$this->assertSame( 42, $this->invoke( 42 ) );
35+
$this->assertNull( $this->invoke( null ) );
36+
}
37+
38+
public function testEmptyArray(): void {
39+
$this->assertSame( [], $this->invoke( [] ) );
40+
}
41+
42+
public function testZeroIndexedListShiftsToOneIndexed(): void {
43+
$this->assertSame(
44+
[ 1 => 'a', 2 => 'b', 3 => 'c' ],
45+
$this->invoke( [ 'a', 'b', 'c' ] )
46+
);
47+
}
48+
49+
public function testAssociativeKeysUnchanged(): void {
50+
$this->assertSame(
51+
[ 'foo' => 'a', 'bar' => 'b' ],
52+
$this->invoke( [ 'foo' => 'a', 'bar' => 'b' ] )
53+
);
54+
}
55+
56+
public function testMixedKeys(): void {
57+
// Integer keys shift by 1; string keys are left untouched by array_unshift().
58+
$this->assertSame(
59+
[ 1 => 'a', 'foo' => 'b', 2 => 'c' ],
60+
$this->invoke( [ 0 => 'a', 'foo' => 'b', 1 => 'c' ] )
61+
);
62+
}
63+
64+
public function testNested(): void {
65+
$expected = [ 1 => [ 1 => 'inner-a', 2 => 'inner-b' ] ];
66+
$this->assertSame( $expected, $this->invoke( [ [ 'inner-a', 'inner-b' ] ] ) );
67+
}
68+
69+
public function testDeeplyNested(): void {
70+
$input = [ [ [ 'leaf' ] ] ];
71+
$expected = [ 1 => [ 1 => [ 1 => 'leaf' ] ] ];
72+
$this->assertSame( $expected, $this->invoke( $input ) );
73+
}
74+
75+
}

0 commit comments

Comments
 (0)