Performance Guide

Tips and best practices for writing efficient Scriptling code.

String Concatenation

The Problem

String concatenation with += in loops is slow because each operation creates a new string object:

# SLOW - Creates 1000 string objects
result = ""
for i in range(1000):
    result += str(i)  # Each += creates a new string

The exact cost depends on the Scriptling version, Go version, platform, and input size. Compare alternatives with the same input and iteration count rather than relying on fixed timings.

Solution: Use join()

# FAST - Efficient and Python-compatible
parts = []
for i in range(1000):
    parts.append(str(i))
result = "".join(parts)

Why it’s usually faster: join() can allocate the result once and copy the strings in one pass. Verify the benefit for your workload with the benchmark helper below.

When += is OK

# OK - Fine for small numbers
result = "hello" + " " + "world"
# or
result = "hello"
result += " "
result += "world"

When to use:

  • Concatenating < 10 strings
  • Outside of loops
  • Readability matters more

Examples

Building CSV

# SLOW
csv = ""
for row in data:
    csv += ",".join(row) + "\n"

# FAST - Using join()
lines = []
for row in data:
    lines.append(",".join(row))
csv = "\n".join(lines)

Building HTML

# SLOW
html = "<ul>"
for item in items:
    html += "<li>" + item + "</li>"
html += "</ul>"

# FAST - Using join()
parts = ["<ul>"]
for item in items:
    parts.append("<li>" + item + "</li>")
parts.append("</ul>")
html = "".join(parts)

Building JSON-like Strings

# SLOW
json_str = "["
for i, item in enumerate(items):
    if i > 0:
        json_str += ", "
    json_str += "\"" + item + "\""
json_str += "]"

# FAST - Using join()
parts = ["\"" + item + "\"" for item in items]
json_str = "[" + ", ".join(parts) + "]"

# EVEN BETTER - Use json library
import json
json_str = json.dumps(items)

Recursion vs Iteration

The Problem

Deep recursion creates many function call frames and environment copies:

# SLOW - Deep recursion
def fib(n):
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

result = fib(10)

Exact timing and allocation counts vary by release and platform; compare recursive and iterative versions with the same input using the helper below.

Solution: Use Iteration

# FAST - Iterative approach
def fib(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

result = fib(10)  # Much faster, constant memory

When Recursion is OK

Recursion is fine for:

  • Tree/graph traversal (limited depth)
  • Divide-and-conquer algorithms
  • Naturally recursive problems with small depth

Avoid recursion for:

  • Problems with deep recursion (> 100 levels)
  • Problems that can be solved iteratively
  • Performance-critical code

List Operations

Pre-allocate When Size is Known

# SLOW - Multiple reallocations
items = []
for i in range(1000):
    items.append(i)

# OK - List comprehension (often faster)
items = [i for i in range(1000)]

Avoid Repeated lookups

# SLOW - Looks up data["items"] each time
for i in range(len(data["items"])):
    process(data["items"][i])

# FAST - Cache the reference
items = data["items"]
for i in range(len(items)):
    process(items[i])

# EVEN BETTER - Iterate directly
for item in data["items"]:
    process(item)

Dictionary Operations

Use get() for Optional Keys

# SLOW - Exception handling overhead
try:
    value = data["key"]
except KeyError:
    value = default

# FAST - No exception overhead
value = data.get("key", default)

Check Membership Efficiently

# FAST - O(1) lookup
if key in data:
    value = data[key]

# Also FAST - Single lookup with default
value = data.get(key)
if value is not None:
    # Use value

General Tips

  1. Profile before optimizing: Use benchmarks to find real bottlenecks
  2. Readability first: Optimize only when needed
  3. Use built-in functions: They’re already optimized
  4. Avoid premature optimization: Write clear code first
  5. Prefer iteration over recursion: For deep operations
  6. Use join() for string building: In loops

Benchmarking Your Code

import time

# Measure execution time
start = time.time()

# Your code here
result = []
for i in range(10000):
    result.append(i)

end = time.time()
print("Took " + str((end - start) * 1000) + " ms")

Benchmark Function

def benchmark(name, func, iterations=1000, warmups=3):
    import time

    for _ in range(warmups):
        func()

    start = time.time()
    for _ in range(iterations):
        func()
    elapsed_ms = (time.time() - start) * 1000
    print(name + ": " + str(elapsed_ms / iterations) + " ms/iter")

# Compare the same workload with the same measured iteration count.
benchmark("string += ", func=lambda: slow_concat(), iterations=1000)
benchmark("join()    ", func=lambda: fast_concat(), iterations=1000)

Treat these measurements as local results, not portable constants. Record the Scriptling version, Go version, operating system/architecture, input size, warmup count, and measured iteration count when publishing figures.

Summary

Pattern Avoid Prefer
String building in loops result += str(i) "".join(parts)
Deep recursion fib(n-1) + fib(n-2) Iterative loop
Optional dict access try/except KeyError dict.get(key, default)
List building Multiple append() in simple cases List comprehension
Repeated lookups data["key"] in loop Cache reference

See Also