speed opts

This commit is contained in:
Will Abbott 2024-09-14 08:13:44 +01:00
parent b2e9cdac68
commit 8705b95455
3 changed files with 134 additions and 110 deletions

View file

@ -1 +1,3 @@
{% for d in data %}<c-benchmarks.cotton-include />{% endfor %}
{#{% for d in data %}<c-benchmarks.cotton-include />{% endfor %}#}
{% load cache %}
{% for d in data %}{% comp benchmarks.cotton-include %}{% endcomp %}{% endfor %}

View file

@ -1,105 +1,115 @@
import time
import django
from django.conf import settings
from django.template.loader import render_to_string
# Configure Django settings
settings.configure(
INSTALLED_APPS=[
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django_cotton",
],
TEMPLATES=[
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["example_project/templates"],
"APP_DIRS": True,
"OPTIONS": {
"loaders": [
(
"django.template.loaders.cached.Loader",
[
"django_cotton.loader.CottonLoader",
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
)
],
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
def configure_django():
from django.conf import settings
settings.configure(
INSTALLED_APPS=[
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django_cotton.apps.SimpleAppConfig",
],
TEMPLATES=[
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": ["example_project/templates"],
# "APP_DIRS": True,
"OPTIONS": {
"loaders": [
(
"django.template.loaders.cached.Loader",
[
"django_cotton.cotton_loader.Loader",
"django.template.loaders.filesystem.Loader",
"django.template.loaders.app_directories.Loader",
],
),
],
"builtins": [
"django_cotton.templatetags.cotton",
"django_cotton.templatetags.tags",
],
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
},
],
COTTON_TEMPLATE_CACHING_ENABLED=False,
DEBUG=False,
)
],
DEBUG=False,
)
django.setup()
import django
django.setup()
print(f"DEBUG: {settings.DEBUG}")
def template_bench(template_name, iterations=500):
start_time = time.time()
for _ in range(iterations):
render_to_string(template_name)
end_time = time.time()
duration = round((end_time - start_time) * 1000, 2)
def main():
configure_django()
return duration, render_to_string(template_name)
def template_bench(template_name, iterations=500):
start_time = time.time()
for _ in range(iterations):
render_to_string(template_name)
end_time = time.time()
duration = round((end_time - start_time) * 1000, 2)
return duration, render_to_string(template_name)
def template_bench_data_loop(template_name, iterations=500):
data = list(range(1, iterations))
start_time = time.time()
render_to_string(template_name, context={"data": data})
end_time = time.time()
duration = round((end_time - start_time) * 1000, 2)
return duration, render_to_string(template_name)
# warm caches
template_bench_data_loop("simple_native.html", iterations=1)
template_bench_data_loop("simple_cotton.html", iterations=1)
simple_native, _ = template_bench_data_loop("simple_native.html")
simple_cotton, _ = template_bench_data_loop("simple_cotton.html")
print("---")
print(f"Native Django {{% for %}} loop: {simple_native} ms")
print(f"Cotton {{% for %}} loop: {simple_cotton} ms")
# warm caches
template_bench("benchmarks/native_include.html", iterations=1)
template_bench("cotton/benchmarks/cotton_include.html", iterations=1)
time_native_include, _ = template_bench("benchmarks/native_include.html")
time_cotton_include, _ = template_bench("cotton/benchmarks/cotton_include.html")
print("---")
print(f"Native {{% include %}}: {time_native_include} ms")
print(f"Cotton for include: {time_cotton_include} ms")
# warm caches
template_bench("benchmarks/native_extends.html", iterations=1)
template_bench("cotton/benchmarks/cotton_compiled.html", iterations=1)
template_bench("cotton/benchmarks/cotton_extends_equivalent.html", iterations=1)
time_native_extends, _ = template_bench("benchmarks/native_extends.html")
time_compiled_cotton, _ = template_bench("cotton/benchmarks/cotton_compiled.html")
time_cotton, _ = template_bench("cotton/benchmarks/cotton_extends_equivalent.html")
print("---")
print(f"Native {{% block %}} and {{% extends %}}: {time_native_extends} ms")
print(f"Compiled Cotton Template: {time_compiled_cotton} ms")
print(f"Uncompiled Cotton Template: {time_cotton} ms")
def template_bench_alt(template_name, iterations=500):
data = list(range(1, iterations))
start_time = time.time()
render_to_string(template_name, context={"data": data})
end_time = time.time()
duration = round((end_time - start_time) * 1000, 2)
return duration, render_to_string(template_name)
# warm caches
template_bench_alt("simple_native.html", iterations=1)
template_bench_alt("simple_cotton.html", iterations=1)
simple_native, _ = template_bench_alt("simple_native.html")
simple_cotton, _ = template_bench_alt("simple_cotton.html")
print("---")
print(f"Native Django {{% for %}} loop: {simple_native} ms")
print(f"Cotton {{% for %}} loop: {simple_cotton} ms")
# warm caches
template_bench("benchmarks/native_include.html", iterations=1)
template_bench("cotton/benchmarks/cotton_include.html", iterations=1)
time_native_include, _ = template_bench("benchmarks/native_include.html")
time_cotton_include, _ = template_bench("cotton/benchmarks/cotton_include.html")
print("---")
print(f"Native {{% include %}}: {time_native_include} ms")
print(f"Cotton for include: {time_cotton_include} ms")
# warm caches
template_bench("benchmarks/native_extends.html", iterations=1)
template_bench("cotton/benchmarks/cotton_compiled.html", iterations=1)
template_bench("cotton/benchmarks/cotton_extends_equivalent.html", iterations=1)
time_native_extends, _ = template_bench("benchmarks/native_extends.html")
time_compiled_cotton, _ = template_bench("cotton/benchmarks/cotton_compiled.html")
time_cotton, _ = template_bench("cotton/benchmarks/cotton_extends_equivalent.html")
print("---")
print(f"Native {{% block %}} and {{% extends %}}: {time_native_extends} ms")
print(f"Compiled Cotton Template: {time_compiled_cotton} ms")
print(f"Uncompiled Cotton Template: {time_cotton} ms")
if __name__ == "__main__":
main()

View file

@ -1,4 +1,5 @@
import ast
import functools
from collections.abc import Mapping
from typing import Union, Set
@ -131,6 +132,7 @@ class CottonComponentNode(Node):
self.component_name = component_name
self.nodelist = nodelist
self.attrs = attrs
self.template_cache = {}
def render(self, context):
cotton_data = get_cotton_data(context)
@ -162,9 +164,6 @@ class CottonComponentNode(Node):
# Process dynamic attributes from named slots
for slot_name, slot_content in component_data["slots"].items():
if slot_name.startswith(":"):
# attr_name = slot_name[1:] # Remove the ':' prefix
# resolved_value = self._process_dynamic_value(slot_content, context)
# component_data["attrs"][attr_name] = resolved_value
if isinstance(slot_content, DynamicAttr):
component_data["attrs"][slot_name[1:]] = slot_content.resolve(context)
else:
@ -178,15 +177,7 @@ class CottonComponentNode(Node):
**component_data["attrs"],
}
# template_name = f"{self.component_name.replace('-', '_')}.html"
template_name = self._generate_component_template_path(
self.component_name, component_data["attrs"].get("is")
)
# Use the base.Template of a backends.django.Template.
template = get_template(template_name)
if hasattr(template, "template"):
template = template.template
template = self._get_cached_template(context)
# Render the template with the new context
with context.push(**cotton_specific):
@ -224,7 +215,26 @@ class CottonComponentNode(Node):
# Flag as unprocessable if none of the above worked
return UnprocessableValue(value)
def _generate_component_template_path(self, component_name: str, is_: Union[str, None]) -> str:
def _get_cached_template(self, context):
cache = context.render_context.get(self)
if cache is None:
cache = context.render_context[self] = {}
template_path = self._generate_component_template_path(
self.component_name, self.attrs.get("is")
)
if template_path not in cache:
template = get_template(template_path)
if hasattr(template, "template"):
template = template.template
cache[template_path] = template
return cache[template_path]
@staticmethod
@functools.lru_cache(maxsize=400)
def _generate_component_template_path(component_name: str, is_: Union[str, None]) -> str:
"""Generate the path to the template for the given component name."""
if component_name == "component":
if is_ is None:
@ -292,7 +302,9 @@ class CottonSlotNode(Node):
if cotton_data["stack"]:
content = self.nodelist.render(context)
if self.is_expression:
cotton_data["stack"][-1]["slots"][self.slot_name] = DynamicAttr(content)
cotton_data["stack"][-1]["slots"][self.slot_name] = DynamicAttr(content).resolve(
context
)
else:
cotton_data["stack"][-1]["slots"][self.slot_name] = mark_safe(content)
return ""