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 stringThe 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 memoryWhen 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 valueGeneral Tips
- Profile before optimizing: Use benchmarks to find real bottlenecks
- Readability first: Optimize only when needed
- Use built-in functions: They’re already optimized
- Avoid premature optimization: Write clear code first
- Prefer iteration over recursion: For deep operations
- 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
- Built-in Functions - String and list functions