فهرست مقاله
۱. آنچه تابع نمیبیند در کلید ذخیره نمیشود

فرض کنید یک مرحلهٔ پردازش، شمارشِ خام را به میکروولت تبدیل میکند و نتیجهٔ فراخوانیهای تکراری را نگه میدارد. سپس ضرایبِ کالیبراسیون تغییر میکنند، اما عددِ خام همان است. اگر ضرایب از متغیری بیرونِ تابع خوانده شوند، فراخوانیِ دوباره ممکن است همان نتیجهٔ قبلی را بدهد. این درس چنین خطایی را با دادهٔ ساختگی آشکار میکند؛ پرسشِ اصلی، کاملبودنِ هویتِ ورودی و نگهداریِ نتیجه است، نه تندترشدنِ برنامه.
در شاهدِ مسئلهدار، تنها شمارشِ ۸ وارد تابعِ دارای lru_cache میشود. نسخهٔ نخست، شیبِ ۱۲۵ میکروولتبرشمارش و عرضِ ازمبدأِ منفیِ ۲۵۰ میکروولت دارد؛ بنابراین پاسخ ۷۵۰ میکروولت است. نسخهٔ دوم با شیبِ ۱۲۶ و عرضِ ازمبدأِ منفیِ ۱۰۰، پاسخِ ۹۰۸ میکروولت میدهد. این اعداد فقط دو قانونِ حسابیِ قراردادیاند؛ قرائتِ ADC، مشخصاتِ یک دستگاه یا تأییدِ دقتِ فیزیکی نیستند.
شاهد ابتدا نسخهٔ بیرونی را عوض میکند و همان شمارش را دوباره میپرسد. چون آرگومانِ تابع تغییر نکرده، نتیجهٔ ذخیرهشده دوباره استفاده میشود. فقط پس از پاکسازیِ صریح، بدنه فرصتِ خواندنِ ضریبِ جدید را پیدا میکند. از این مشاهده نباید نتیجه گرفت که کش نسخهٔ مناسب را تشخیص میدهد؛ وابستگیِ پنهان اصلاً بخشی از کلید نبوده است.
قراردادِ رسمیِ lru_cache استفادهٔ دوباره از نتیجهٔ فراخوانی را توضیح میدهد. تابعِ وابسته به وضعیتِ متغیر، با تابعی که نتیجهاش از آرگومانها تعیین میشود یک قرارداد ندارد. همچنین «بهتازگی استفادهشده» دربارهٔ ترتیبِ مراجعه است؛ معنای آن «با ضرایبِ معتبرِ فعلی محاسبهشده» نیست. این تمایز را باید پیش از تفسیرِ شمارِ hit یا انتخابِ ظرفیت روشن کنیم.
۲. نسخهٔ معتبر را به ضرایبِ ثابت وصل کنیم

رابطِ اصلی دو عدد میگیرد: نسخه و شمارش. نسخه فقط عددِ صحیحِ دقیقِ ۱ یا ۲ و شمارش فقط عددِ صحیحِ دقیقِ صفر تا ۴۰۹۵ است. bool، عددِ شناور و زیرکلاسِ int بهصورتِ ضمنی پذیرفته نمیشوند. اعتبارسنجی بیرونِ decorator انجام میشود تا حتی مسیری که میتواند hit باشد نیز از قراردادِ عمومی عبور کند؛ ورودیِ نامعتبر نباید شمارندههای کش را تغییر دهد.
درونِ هر نمونهٔ مستقل، یک تاپلِ ثابتِ ضرایب قرار دارد. نسخهٔ ۱ همیشه به قانونِ نخست و نسخهٔ ۲ همیشه به قانونِ دوم وصل میشود. صرفِ افزودنِ یک برچسبِ نسخه کافی نیست: اگر پشتِ همان برچسب، ضرایبِ متفاوتی جایگزین شوند، فرضِ مثال شکسته است. برنامهٔ واقعی باید مرجعِ نسخهها را تعیین کند؛ این کد مرجعِ بیرونی، انتشارِ ضرایب یا هماهنگیِ چند فرایند را کشف نمیکند.
برای همین شمارشِ ۸، درخواستهای دو نسخه، دو نتیجهٔ مشخص دارند. بازگشت به نسخهٔ قدیمی همچنان ۷۵۰ میکروولت میدهد؛ قدیمیبودنِ شماره به معنیِ نامعتبرشدنِ خودکارِ آن نیست. فراخوان، نسخهٔ دلخواه را صریح انتخاب کرده است. در مثالِ GET شرطی و ETag نیز انتخابِ بازنمایی از مقایسهٔ شناسه جدا بود؛ اینجا هیچ درخواستِ HTTP یا سیاستِ تازگیِ زمانی اجرا نمیشود.
حاصل، تاپلی از نسخه، شمارش و مقدارِ میکروولت است. منفیبودنِ خروجیِ نسخهٔ نخست در شمارشِ صفر و صفرشدنِ آن در شمارشِ دو، در همین مدل مجازند. نتیجهٔ صفر با نبودِ نتیجه برابر نیست. کد هیچ محدودسازیِ دلخواهِ ولتاژ، تبدیلِ واحدِ خودکار یا خطای اندازهگیری به این حسابِ صحیح اضافه نمیکند؛ کرانِ ورودی با دامنهٔ خروجی یکی نیست.
۳. مقدارِ بازگشتی الزاماً شیءِ تازه نیست

کش، ارجاع به آرگومانها و نتیجهها را تا اخراج یا پاکسازی نگه میدارد. در شاهدِ جداگانه، تابع عمداً یک فهرستِ قابلتغییر برمیگرداند. فراخوانِ نخست عضوِ آن را به منفیِ ۹۹۹ عوض میکند و فراخوانِ بعدی همان فهرستِ ذخیرهشده را میبیند. این مقدار، خروجیِ کالیبراسیونِ اصلی نیست؛ ضدنمونهای است برای فرضِ نادرستِ «هر فراخوانی، ظرفِ مستقل میسازد».
تابعِ اصلی فقط تاپلی با عضوهای عددِ صحیح برمیگرداند. تلاش برای جایگزینیِ عضوِ آن خطا میدهد. مدلِ دادهٔ پایتون میان هویت، مقدار و تغییرپذیری تفاوت میگذارد؛ تاپلِ شاملِ فهرست، مسئلهٔ فهرستِ مشترک را حل نمیکند. اینجا عضوهای نتیجه هم تغییرناپذیرند. از این انتخاب، ادعای کپیِ عمیقِ همهٔ انواعِ شیء یا تضمینِ محرمانگی به دست نمیآید.
ظرفیتِ سه، سقفِ تعدادِ مدخلهای همین wrapper است و بودجهٔ بایت یا سقفِ حافظهٔ کلِ برنامه نیست. در آزمایشِ ارجاعِ نگهدارنده و weakref دیدیم که یک مسیرِ دیگر میتواند مقدار را نگه دارد. اینجا نیز پاککردنِ کش، تاپلِ قبلاً تحویلدادهشده را بازنویسی نمیکند. هیچ اندازهگیریِ RSS، tracemalloc، زمانِ آزادسازی یا سنجشِ سرعت در این درس نداریم.
۴. کد کامل و مرجعِ مستقلِ درخواستها

برای بازپخشِ قابلِ بررسی، عملیات یک تاپل با حداکثر هشت عضو است؛ هر عضو، جفتِ نسخه و شمارش یا None برای پاکسازیِ صریح است. کلِ ساختار پیش از ساختِ کش بررسی میشود. مولد، فهرست و ساختارِ ناقص خودکار مصرف یا تبدیل نمیشوند. این مرزِ دستهٔ آزمایشی، محدودیتِ عمومیِ lru_cache نیست و باید از آن جدا خوانده شود.
مرجعِ مستقل، decorator را فراخوانی نمیکند: فهرستی از کلیدهای اخیر، شمارندههای برخورد و خطا و بسطِ صحیحِ دو قانون را نگه میدارد. تمامِ رشتههای طولِ صفر تا پنج از چهار درخواست و یک پاکسازی، یک خانوادهٔ آزموناند. علاوه بر آن، ردِ دستی با چهار کلیدِ متفاوت نشان میدهد ظرفیتِ سه چگونه باعثِ محاسبهٔ دوباره میشود؛ خودِ ساختارِ داخلیِ کش را بازرسی نمیکنیم.
فایلِ کامل با کتابخانهٔ استاندارد نوشته شده و خروجیِ JSON آن شاملِ واحدها، ضرایب، ردِ درخواستها و چهارده گروهِ بولیِ نامدار است. نسخهٔ مستنداتِ خواندهشده 3.14.8 است؛ نسخهٔ اجرای واقعی جداگانه در خروجی ثبت میشود. شمارِ ارزیابیِ بدنه در تمامِ عمرِ نمونه جمع میشود، اما cache_clear شمارندههای hit و miss خودِ کش را بازنشانی میکند. این دو شمارنده را با هم اشتباه نگیرید.
"""Synthetic calibration identity and local memoization, not hardware or timing."""
from functools import lru_cache
from itertools import product
import json
import platform
import sys
def validate_pair(revision, count):
if type(revision) is not int or revision not in (1, 2):
raise ValueError("revision")
if type(count) is not int or not 0 <= count <= 4095:
raise ValueError("count")
class CalibrationCache:
"""Fixed coefficient authority; maxsize counts entries, not memory bytes."""
def __init__(self):
authority = ((125, -250), (126, -100)) # microvolts/count, microvolts
self._evaluations = 0
@lru_cache(maxsize=3)
def evaluate(revision, count):
self._evaluations += 1
slope, offset = authority[revision - 1]
return (revision, count, slope * count + offset)
self._cached = evaluate
def read(self, revision, count):
# Validate outside the decorator, including on a potential cache hit.
validate_pair(revision, count)
return self._cached(revision, count)
def uncached(self, revision, count):
validate_pair(revision, count)
return self._cached.__wrapped__(revision, count)
def clear(self):
self._cached.cache_clear()
def info(self):
info = self._cached.cache_info()
return {"hits": info.hits, "misses": info.misses,
"maxsize": info.maxsize, "currsize": info.currsize,
"lifetime_body_evaluations": self._evaluations}
def parameters(self):
return self._cached.cache_parameters()
def checked_operations(operations):
if type(operations) is not tuple or len(operations) > 8:
raise ValueError("operations")
for operation in operations:
if operation is None: # Explicit local cache_clear operation.
continue
if type(operation) is not tuple or len(operation) != 2:
raise ValueError("operation")
validate_pair(operation[0], operation[1])
return operations
def trace_operations(operations):
operations = checked_operations(operations)
cache = CalibrationCache()
trace = []
for operation in operations:
if operation is None:
cache.clear()
value = None
else:
value = cache.read(*operation)
trace.append({"operation": operation, "result": value, **cache.info()})
return trace
def independent_trace(operations):
"""List recency model; no decorator, production class or validator calls."""
recent = [] # Most recently requested key first.
hits = misses = evaluations = 0
trace = []
for operation in operations:
if operation is None:
recent = []
hits = misses = 0
value = None
else:
revision, count = operation
if operation in recent:
hits += 1
recent.remove(operation)
else:
misses += 1
evaluations += 1
recent.insert(0, operation)
recent = recent[:3]
# Independent integer expansion of the two declared manual rules.
uv = (100 * count + 25 * count - 250 if revision == 1
else 128 * count - 2 * count - 100)
value = (revision, count, uv)
trace.append({"operation": operation, "result": value,
"hits": hits, "misses": misses, "maxsize": 3,
"currsize": len(recent),
"lifetime_body_evaluations": evaluations})
return trace
def stale_dependency_witness():
active = [1]
bodies = []
@lru_cache(maxsize=3)
def incomplete(count):
bodies.append(active[0])
slope, offset = ((125, -250), (126, -100))[active[0] - 1]
return slope * count + offset
try:
old = incomplete(8)
active[0] = 2
stale = incomplete(8)
before_clear = incomplete.cache_info()._asdict()
incomplete.cache_clear()
refreshed = incomplete(8)
return {"first_uv": old, "after_hidden_change_uv": stale,
"after_explicit_clear_uv": refreshed,
"body_revision_observations": bodies,
"before_clear_cache_info": before_clear}
finally:
incomplete.cache_clear()
def mutable_result_witness():
@lru_cache(maxsize=3)
def unsafe(revision, count):
slope, offset = ((125, -250), (126, -100))[revision - 1]
return [slope * count + offset]
try:
first = unsafe(1, 8)
first[0] = -999
second = unsafe(1, 8)
return {"same_cached_list": first is second,
"later_caller_observes_mutation_uv": second[0]}
finally:
unsafe.cache_clear()
def rejects(call, message):
try:
call()
except ValueError as error:
return str(error) == message
return False
def run_example():
cache = CalibrationCache()
old = cache.read(1, 8)
new = cache.read(2, 8)
unchanged_old = cache.read(1, 8)
identity_info = cache.info()
stale = stale_dependency_witness()
before_invalid = cache.info()
class IntChild(int):
def __hash__(self):
raise AssertionError("Rejected before hashing")
invalid_pairs = ((True, 8), (1.0, 8), (IntChild(1), 8), (0, 8),
(3, 8), (None, 8), (1, True), (1, 8.0),
(1, IntChild(8)), (1, -1), (1, 4096), (1, []))
invalid_ok = all(rejects(lambda pair=pair: cache.read(*pair),
"revision" if type(pair[0]) is not int or pair[0] not in (1, 2) else "count")
for pair in invalid_pairs)
invalid_after = cache.info()
class TupleChild(tuple):
def __len__(self):
raise AssertionError("Rejected before length hook")
consumed = []
def lazy_operations():
consumed.append("iterated")
yield (1, 8)
bad_operations = ([], True, TupleChild(), ((1, 8),) * 9)
seq_ok = all(rejects(lambda op=op: trace_operations(op), "operations")
for op in bad_operations)
seq_ok = seq_ok and rejects(lambda: trace_operations(lazy_operations()), "operations")
seq_ok = seq_ok and all(rejects(lambda op=op: trace_operations((op,)), "operation")
for op in ([], (), (1,), (1, 8, 0), TupleChild((1, 8))))
seq_ok = seq_ok and rejects(lambda: trace_operations(((1, 8), (3, 8))), "revision")
seq_ok = seq_ok and rejects(lambda: trace_operations(((1, 8), (1, -1))), "count")
held = cache.read(1, 8)
evaluations_before_clear = cache.info()["lifetime_body_evaluations"]
cache.clear()
cleared = cache.info()
held_after_clear = held
recomputed = cache.read(1, 8)
after_recompute = cache.info()
separate = CalibrationCache()
separate.read(2, 8)
separate_before = separate.info()
cache.clear()
separate_after = separate.info()
immutable = CalibrationCache().read(1, 8)
try:
immutable[2] = 0
except TypeError:
immutable_rejected = True
else:
immutable_rejected = False
alias = mutable_result_witness()
zero_cache = CalibrationCache()
negative = zero_cache.read(1, 0)
zero_a = zero_cache.read(1, 2)
zero_b = zero_cache.read(1, 2)
zeros = zero_cache.info()
instrumented = CalibrationCache()
parameters = instrumented.parameters()
parameters["maxsize"] = 999
parameters["typed"] = True
informational_only = instrumented.parameters() == {"maxsize": 3, "typed": False}
info_copy = instrumented.info()
info_copy["currsize"] = 999
info_fresh = instrumented.info()["currsize"] == 0
hand_ops = ((1, 0), (1, 8), (2, 8), (1, 0), (2, 9), (1, 8))
hand_trace = trace_operations(hand_ops)
bypass = CalibrationCache()
bypass.read(1, 8)
prior_bypass = bypass.info()
bypass_result = bypass.uncached(1, 8)
after_bypass = bypass.info()
bypass_validation = rejects(lambda: bypass.uncached(True, 8), "revision")
choices = ((1, 0), (1, 8), (2, 8), (2, 9), None)
profiles = steps = 0
family_ok = True
for length in range(6):
for operations in product(choices, repeat=length):
actual = trace_operations(operations)
expected = independent_trace(operations)
family_ok = family_ok and actual == expected
profiles += 1
steps += length
endpoint_trace = trace_operations(((2, 4095),) * 8)
checks = {
"manual_same_count_different_revision_has_different_microvolts":
old == (1, 8, 750) and new == (2, 8, 908),
"hidden_mutable_dependency_stays_stale_until_local_clear":
stale["first_uv"] == stale["after_hidden_change_uv"] == 750
and stale["after_explicit_clear_uv"] == 908
and stale["body_revision_observations"] == [1, 2],
"old_revision_keeps_its_declared_meaning_after_new_revision":
unchanged_old == old and identity_info == {"hits": 1, "misses": 2, "maxsize": 3, "currsize": 2, "lifetime_body_evaluations": 2},
"repeat_valid_key_hits_without_body_reexecution":
zeros["hits"] == 1 and zeros["misses"] == 2 and zeros["lifetime_body_evaluations"] == 2,
"invalid_pair_types_and_bounds_do_not_touch_cache":
invalid_ok and invalid_after == before_invalid,
"strict_operation_batch_rejects_before_consuming_generator":
seq_ok and consumed == [] and trace_operations(()) == [],
"clear_resets_cache_statistics_but_not_held_value_or_lifetime_counter":
held_after_clear == held == (1, 8, 750)
and cleared == {"hits": 0, "misses": 0, "maxsize": 3, "currsize": 0, "lifetime_body_evaluations": evaluations_before_clear}
and recomputed == held and after_recompute["misses"] == 1
and after_recompute["lifetime_body_evaluations"] == evaluations_before_clear + 1,
"clear_is_local_to_one_wrapper":
separate_before == separate_after and separate.read(2, 8) == (2, 8, 908) and separate.info()["hits"] == 1,
"immutable_main_output_and_shared_mutable_result_counterexample":
immutable_rejected and immutable == (1, 8, 750)
and alias == {"same_cached_list": True, "later_caller_observes_mutation_uv": -999},
"zero_and_negative_microvolts_are_valid_results_not_misses":
negative == (1, 0, -250) and zero_a == zero_b == (1, 2, 0),
"instrumentation_returns_informational_fresh_mappings":
informational_only and info_fresh,
"manual_access_refreshes_recency_before_fourth_distinct_key":
[row["hits"] for row in hand_trace] == [0, 0, 0, 1, 1, 1]
and [row["misses"] for row in hand_trace] == [1, 2, 3, 3, 4, 5]
and [row["currsize"] for row in hand_trace] == [1, 2, 3, 3, 3, 3],
"bypass_computes_without_cache_hit_miss_or_size_change":
bypass_result == (1, 8, 750) and bypass_validation
and {k: v for k, v in prior_bypass.items() if k != "lifetime_body_evaluations"} == {k: v for k, v in after_bypass.items() if k != "lifetime_body_evaluations"}
and after_bypass["lifetime_body_evaluations"] == prior_bypass["lifetime_body_evaluations"] + 1,
"independent_list_model_all3906_profiles_inside_ONE_group":
bool(family_ok) and profiles == 3906 and steps == 18555
and len(endpoint_trace) == 8 and endpoint_trace[-1]["result"] == (2, 4095, 515870)
and endpoint_trace[-1]["hits"] == 7,
}
assert len(checks) == 14 and all(type(v) is bool and v for v in checks.values()), checks
return {"runtime": {"python": platform.python_version(), "implementation": platform.python_implementation()},
"units": {"input": "synthetic ADC count", "slope": "microvolts per count", "offset_and_output": "integer microvolts"},
"fixed_authority": [{"revision": 1, "slope_uv_per_count": 125, "offset_uv": -250},
{"revision": 2, "slope_uv_per_count": 126, "offset_uv": -100}],
"limits": {"revision": "exact int1or2", "count": "exact int0..4095", "operations": "exact tuple0..8; each None clear or exact tuple(revision,count)", "cache_entries": 3},
"manual_versioned_results": [old, new, unchanged_old],
"hidden_dependency": stale, "shared_mutable_result": alias,
"manual_request_trace": hand_trace,
"independent_family": {"profiles_inside_ONE_group": profiles, "observed_steps": steps, "lengths": "0..5", "choices": 5},
"invalid_pair_cases_inside_ONE_group": len(invalid_pairs),
"meaningful_named_groups": len(checks), "checks": checks,
"scope": "Synthetic synchronous local function cache; entry count not bytes. Immutable fixed revision meaning. No HTTP, timing, memory profiling, RSS, hardware, physical accuracy, process coherence or concurrent exactly-once claim."}
if __name__ == "__main__":
sys.stdout.buffer.write((json.dumps(run_example(), ensure_ascii=True, indent=2) + "\n").encode("utf-8"))
{
"runtime": {
"python": "3.14.5",
"implementation": "CPython"
},
"units": {
"input": "synthetic ADC count",
"slope": "microvolts per count",
"offset_and_output": "integer microvolts"
},
"fixed_authority": [
{
"revision": 1,
"slope_uv_per_count": 125,
"offset_uv": -250
},
{
"revision": 2,
"slope_uv_per_count": 126,
"offset_uv": -100
}
],
"limits": {
"revision": "exact int1or2",
"count": "exact int0..4095",
"operations": "exact tuple0..8; each None clear or exact tuple(revision,count)",
"cache_entries": 3
},
"manual_versioned_results": [
[
1,
8,
750
],
[
2,
8,
908
],
[
1,
8,
750
]
],
"hidden_dependency": {
"first_uv": 750,
"after_hidden_change_uv": 750,
"after_explicit_clear_uv": 908,
"body_revision_observations": [
1,
2
],
"before_clear_cache_info": {
"hits": 1,
"misses": 1,
"maxsize": 3,
"currsize": 1
}
},
"shared_mutable_result": {
"same_cached_list": true,
"later_caller_observes_mutation_uv": -999
},
"manual_request_trace": [
{
"operation": [
1,
0
],
"result": [
1,
0,
-250
],
"hits": 0,
"misses": 1,
"maxsize": 3,
"currsize": 1,
"lifetime_body_evaluations": 1
},
{
"operation": [
1,
8
],
"result": [
1,
8,
750
],
"hits": 0,
"misses": 2,
"maxsize": 3,
"currsize": 2,
"lifetime_body_evaluations": 2
},
{
"operation": [
2,
8
],
"result": [
2,
8,
908
],
"hits": 0,
"misses": 3,
"maxsize": 3,
"currsize": 3,
"lifetime_body_evaluations": 3
},
{
"operation": [
1,
0
],
"result": [
1,
0,
-250
],
"hits": 1,
"misses": 3,
"maxsize": 3,
"currsize": 3,
"lifetime_body_evaluations": 3
},
{
"operation": [
2,
9
],
"result": [
2,
9,
1034
],
"hits": 1,
"misses": 4,
"maxsize": 3,
"currsize": 3,
"lifetime_body_evaluations": 4
},
{
"operation": [
1,
8
],
"result": [
1,
8,
750
],
"hits": 1,
"misses": 5,
"maxsize": 3,
"currsize": 3,
"lifetime_body_evaluations": 5
}
],
"independent_family": {
"profiles_inside_ONE_group": 3906,
"observed_steps": 18555,
"lengths": "0..5",
"choices": 5
},
"invalid_pair_cases_inside_ONE_group": 12,
"meaningful_named_groups": 14,
"checks": {
"manual_same_count_different_revision_has_different_microvolts": true,
"hidden_mutable_dependency_stays_stale_until_local_clear": true,
"old_revision_keeps_its_declared_meaning_after_new_revision": true,
"repeat_valid_key_hits_without_body_reexecution": true,
"invalid_pair_types_and_bounds_do_not_touch_cache": true,
"strict_operation_batch_rejects_before_consuming_generator": true,
"clear_resets_cache_statistics_but_not_held_value_or_lifetime_counter": true,
"clear_is_local_to_one_wrapper": true,
"immutable_main_output_and_shared_mutable_result_counterexample": true,
"zero_and_negative_microvolts_are_valid_results_not_misses": true,
"instrumentation_returns_informational_fresh_mappings": true,
"manual_access_refreshes_recency_before_fourth_distinct_key": true,
"bypass_computes_without_cache_hit_miss_or_size_change": true,
"independent_list_model_all3906_profiles_inside_ONE_group": true
},
"scope": "Synthetic synchronous local function cache; entry count not bytes. Immutable fixed revision meaning. No HTTP, timing, memory profiling, RSS, hardware, physical accuracy, process coherence or concurrent exactly-once claim."
}
۵. چهار پرسش دربارهٔ پاکسازی و مرزِ نگهداری

پرسش اول: آیا شمارهٔ نسخه بدونِ مرجعِ ثابت، نتیجه را معتبر میکند؟ پاسخ: خیر. نسخه باید واقعاً ضرایبِ مؤثر را مشخص کند. در این مثال، همان شمارش با نسخهٔ یک ۷۵۰ و با نسخهٔ دو ۹۰۸ میکروولت است. اگر ضرایبِ نسخهٔ یک را بیصدا عوض کنیم، دیگر قراردادِ تابعِ اصلی را اجرا نکردهایم؛ کش وظیفهٔ کشفِ آن تغییر را ندارد.
پرسش دوم: پس از پاکسازی، تاپلِ قدیمی چه میشود؟ پاسخ: اگر فراخوان آن را نگه دارد، همان مقدارِ قبلی باقی میماند. پاکسازیِ wrapper محلی، مدخلها و آمارِ خودش را پاک میکند؛ نه نسخهٔ انتخابی را تغییر میدهد و نه مقدارِ بیرونِ کش را بهروز میکند. شاهدِ کد، نتیجهٔ نگهداشتهشده را بعد از پاکسازی با مقدارِ اولیه مقایسه و درخواستِ بعدی را یک miss تازه ثبت میکند.
پرسش سوم: آیا میتوان برای وارسی، محاسبه را بدونِ استفاده از کش انجام داد؟ پاسخ: مسیرِ uncached پس از همان اعتبارسنجی، تابعِ زیرین را فراخوانی میکند. مقدارِ محاسبه با نتیجهٔ قراردادی برابر است، اما hit، miss و اندازهٔ کش تغییر نمیکنند؛ تنها شمارِ ارزیابیِ بدنه زیاد میشود. این مسیر، آزمونِ دقتِ سختافزار یا روشِ تازهسازیِ هماهنگِ همهٔ نمونهها نیست.
پرسش چهارم: آیا hit زیاد یا ظرفیتِ سه، صرفهجویی را ثابت میکند؟ پاسخ: خیر. هزینهٔ واقعیِ محاسبه، سربارِ کش، الگوی مراجعه و حافظهٔ نگهداشتهشده اندازهگیری نشدهاند. حتی صفر و مقدارِ منفی نیز در این مدل نتیجههای مجاز و قابلِ استفادهٔ دوبارهاند. کاربردِ تمرین آن است که پیش از انتخابِ کش برای یک مرحلهٔ پردازش، وابستگیِ پنهان، معنای نسخه و مالکیتِ نتیجه را با شاهدی محدود روشن کنیم.
منابع و مطالعهٔ بیشتر
بررسی منابع: ۲۹ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

