Skip to content

Assign changed localized value directly#109

Open
topaxi wants to merge 1 commit into
SectorLabs:masterfrom
topaxi:perf/value-set
Open

Assign changed localized value directly#109
topaxi wants to merge 1 commit into
SectorLabs:masterfrom
topaxi:perf/value-set

Conversation

@topaxi

@topaxi topaxi commented Jul 4, 2026

Copy link
Copy Markdown

Calling self.__dict__.update(self) causes repetetive iterations over all configured language keys. The default in Django is 99, which causes this update to grow quadratic.

Stumbled upon this for a larger object graph (~500+ entities), where each entity has multiple localized fields.

repro.py
"""Reproduces O(n^2) LocalizedValue construction high language count (99,
django default).
"""

import time

import django
from django.conf import settings
from localized_fields.value import LocalizedValue

settings.configure(USE_I18N=True, LANGUAGE_CODE="lang0", LANGUAGES=[])
django.setup()

LANGUAGE_COUNT = 99

settings.LANGUAGES = [(f"lang{i}", f"Language {i}") for i in range(LANGUAGE_COUNT)]
settings.LANGUAGE_CODE = "lang0"

FIELDS_PER_ENTITY = 10
DB_VALUE = {"lang0": "some value", "lang1": "ein Wert"}


def build_entity():
    return [LocalizedValue(DB_VALUE) for _ in range(FIELDS_PER_ENTITY)]


def time_graph(num_entities):
    start = time.perf_counter()
    for _ in range(num_entities):
        build_entity()
    return time.perf_counter() - start


def run(label):
    print(
        f"\n{label} ({len(settings.LANGUAGES)} languages, "
        f"{FIELDS_PER_ENTITY} localized fields/entity)"
    )
    print(f"{'entities':>10} {'seconds':>12} {'ms/entity':>12}")
    for n in (10, 50, 100, 500, 1000, 2000):
        elapsed = time_graph(n)
        print(f"{n:>10} {elapsed:>12.4f} {elapsed / n * 1000:>12.4f}")


run("before fix (original LocalizedValue.set)")


def fixed_set(self, language, value):
    """Only touch the key that changed, instead of re-copying the whole dict."""
    self[language] = value
    self.__dict__[language] = value
    return self


LocalizedValue.set = fixed_set

run("after fix (patched LocalizedValue.set)")
repro.py summary with 99 languages
before fix (original LocalizedValue.set) (99 languages, 10 localized fields/entity)
  entities      seconds    ms/entity
        10       0.0420       4.2035
        50       0.2104       4.2073
       100       0.4197       4.1967
       500       2.1028       4.2055
      1000       4.2038       4.2038
      2000       4.6492       2.3246

after fix (patched LocalizedValue.set) (99 languages, 10 localized fields/entity)
  entities      seconds    ms/entity
        10       0.0027       0.2707
        50       0.0133       0.2658
       100       0.0268       0.2682
       500       0.1338       0.2676
      1000       0.2676       0.2676
      2000       0.5359       0.2679
repro.py summary with 3 languages (no regression)
before fix (original LocalizedValue.set) (3 languages, 10 localized fields/entity)
  entities      seconds    ms/entity
        10       0.0005       0.0499
        50       0.0024       0.0477
       100       0.0045       0.0445
       500       0.0228       0.0456
      1000       0.0436       0.0436
      2000       0.0891       0.0445

after fix (patched LocalizedValue.set) (3 languages, 10 localized fields/entity)
  entities      seconds    ms/entity
        10       0.0003       0.0331
        50       0.0015       0.0310
       100       0.0030       0.0299
       500       0.0153       0.0306
      1000       0.0306       0.0306
      2000       0.0615       0.0307

Calling `self.__dict__.update(self)` causes repetetive iterations over
all configured language keys. The default in Django is 99, which causes
this update to grow exponentially.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant