From df46a928053235e3050f98e99df47ae0372942d2 Mon Sep 17 00:00:00 2001 From: 1000000000 Date: Thu, 1 Apr 2021 21:54:28 -0400 Subject: [PATCH 1/3] String.join can be replaced with a faster version --- src/transforms/replaceStringFunctions.ts | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/transforms/replaceStringFunctions.ts diff --git a/src/transforms/replaceStringFunctions.ts b/src/transforms/replaceStringFunctions.ts new file mode 100644 index 0000000..bd80298 --- /dev/null +++ b/src/transforms/replaceStringFunctions.ts @@ -0,0 +1,40 @@ +import ts, { isIdentifier } from 'typescript'; +import { ast } from './utils/create'; + +const $elm$core$String$join = ` +var $elm$core$String$join = F2(function (sep, strs) { + if (!strs.b) { + return ""; + } + var acc = strs.a; + strs = strs.b; + + for (; strs.b; strs = strs.b) { + acc = acc + sep + strs.a; + } + + return acc; +}; +`; + + +const replacements = { + $elm$core$String$join, +}; + +export const replaceStringFunctions: ts.TransformerFactory = ( + context +) => (sourceFile) => { + const visitor = (node: ts.Node): ts.VisitResult => { + if (ts.isVariableStatement(node)) { + const name = node.declarationList.declarations[0]?.name; + if (isIdentifier(name) && name.text in replacements) { + const key = name.text as keyof typeof replacements; + return ast(replacements[key]); + } + } + return ts.visitEachChild(node, visitor, context); + }; + + return ts.visitNode(sourceFile, visitor); +}; From 01e539fb1fde9aff3a9c25cb8e992eb22a8f0b97 Mon Sep 17 00:00:00 2001 From: 1000000000 Date: Thu, 1 Apr 2021 21:57:43 -0400 Subject: [PATCH 2/3] Wired in String.join transformation --- src/transform.ts | 2 ++ src/types.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/transform.ts b/src/transform.ts index 9ec4585..ee1bd7c 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -23,6 +23,7 @@ import { createPassUnwrappedFunctionsTransformer } from './transforms/passUnwrap import { replaceVDomNode } from './transforms/adjustVirtualDom'; import { inlineNumberToString } from './transforms/inlineNumberToString'; import { replaceListFunctions } from './transforms/replaceListFunctions'; +import { replaceStringFunctions } from './transforms/replaceStringFunctions'; import { reportFunctionStatusInBenchmarks, v8Debug } from './transforms/analyze'; export type Options = { @@ -80,6 +81,7 @@ export const transform = async ( let inlineCtx: InlineContext | undefined; const transformations: any[] = removeDisabled([ [transforms.replaceListFunctions, replaceListFunctions], + [transforms.replaceStringFunctions, replaceStringFunctions], [transforms.v8Analysis, v8Debug], [transforms.variantShapes, normalizeVariantShapes], diff --git a/src/types.ts b/src/types.ts index a2e8a1b..92ac0dd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,7 @@ export type Transforms = { objectUpdate: ObjectUpdate | false; unusedValues: boolean; replaceListFunctions: boolean; + replaceStringFunctions: boolean; v8Analysis: boolean; }; @@ -77,5 +78,6 @@ export const toolDefaults: Transforms = { objectUpdate: false, unusedValues: false, replaceListFunctions: false, + replaceStringFunctions: false, v8Analysis: false }; From f8e31fa9221b57f527adb6d0bcd3a9a03ed4393f Mon Sep 17 00:00:00 2001 From: 1000000000 Date: Thu, 1 Apr 2021 22:11:36 -0400 Subject: [PATCH 3/3] Added some documentation for the optimization --- notes/transformations.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/notes/transformations.md b/notes/transformations.md index b841602..aa19921 100644 --- a/notes/transformations.md +++ b/notes/transformations.md @@ -459,3 +459,13 @@ It's pretty common to put things in a tuple (or threeple) to start a case statem ``` We could skip allocating the tuple though. + +# String Joining + +For joining (and concating) strings, Elm uses Javascript's Array join method after converting the list into a Javascript array. +We can replace this implementation with a faster one that traverses the list and builds the string through concatenation instead. + +## Results Summary + +* Not included in elm-optimize-level-2 tool +* The implementation used is similar to the improved String.join implementation [here](https://gitlab.com/e-neighborhood-watch/elm-string-benchmarks/#stringjoin) which sees some serious improvements over Elm's normal String.join.