26 ottobre 2026 · NicoGIS Tech Weekly
Nel software engineering delle applicazioni AI una parte crescente del lavoro non riguarda la scelta del modello, ma il costo di attraversare i confini: dal compilatore al runtime, dalla geometria alla rappresentazione semantica, dal repository all'agente di coding, dal prompt alla memoria del motore di inferenza. Questo numero approfondisce quattro cambiamenti documentati e le loro conseguenze operative. Le informazioni relative a prodotti e release sono distinte dalle analisi, che rappresentano valutazioni architetturali da verificare sul proprio carico di lavoro.
.NET 11 RC2: Runtime Async sposta l'ottimizzazione dal compilatore al JIT
La versione più recente documentata al 26 ottobre è .NET 11 Release Candidate 2, pubblicata il 13 ottobre 2026; la disponibilità generale è prevista per novembre. RC2 dispone di una licenza go-live, ma rimane una release candidate. Il post Microsoft Performance Improvements in .NET 11 è rilevante non tanto per i grafici di velocità, quanto per il cambiamento nel contratto fra C#, IL e runtime.
Fonti: .NET 11 RC2 — release announcement (13 ottobre); Performance Improvements in .NET 11 — Stephen Toub; Microsoft Learn — What's new in .NET 11.
Prima: lo state machine lowering avveniva nel compilatore C#
Nel percorso tradizionale, il compilatore trasforma ogni metodo async in un metodo di ingresso più uno stato compilato in MoveNext, con campi per gli awaiter e le variabili che sopravvivono alla sospensione. Il JIT riceve quindi una struttura già abbassata, complessa da ricondurre al controllo di flusso originale. Nel nuovo Runtime Async, per i metodi eleggibili il compilatore emette IL più compatto, segnala il metodo come asincrono nei metadati e utilizza helper come AsyncHelpers.Await; la trasformazione della sospensione, la liveness dei valori e la gestione delle continuazioni possono essere ottimizzate dal runtime.
Compiler lowering tradizionale
C# async -> tipo state machine + MoveNext -> IL -> JIT
Runtime Async in .NET 11 (opt-in applicativo)
C# async -> IL suspension-aware + metadata -> JIT
|
liveness / continuation layout
task materialization / suspend-resume
La semantica di await, gestione eccezioni, cancellazione e ConfigureAwait non dovrebbe cambiare: è un requisito esplicito del progetto. Cambia invece lo spazio di ottimizzazione. In particolare, il JIT può riconoscere meglio gli strati di delega async, evitando in determinate condizioni lavoro e allocazioni che il codice sorgente non è in grado di eliminare da solo. Microsoft precisa tuttavia che l'obiettivo immediato è la parità o il miglioramento rispetto al lowering tradizionale e che esistono ancora percorsi meno efficienti.
Compatibilità: in .NET 11 Runtime Async si abilita a livello di progetto mediante una feature switch. Sono supportati i ritorni Task, Task<T>, ValueTask e ValueTask<T>; async void, iteratori asincroni e task-like personalizzati continuano a utilizzare il percorso precedente. Non è necessario attivare genericamente tutte le preview feature.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<Features>$(Features);runtime-async=on</Features>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
La misura corretta non è il nanosecondo del microbenchmark
Il post Microsoft documenta anche l'invecchiamento dei dependent handles usati da ConditionalWeakTable. In precedenza gli handle potevano essere visitati ripetutamente nelle garbage collection effimere anche quando entrambi i referenti erano diventati long-lived; ora la classificazione generazionale permette di evitare parte di quel lavoro. Si tratta di un miglioramento potenzialmente significativo per runtime, framework dinamici e caching ricco di associazioni oggetto-oggetto, ma non implica una riduzione uniforme della latenza di tutte le API.
Altre aree toccate includono riduzione dei costi di scheduling dei piccoli work item nel thread pool, analisi di escape nel JIT, ottimizzazioni di Native AOT e il nuovo analyzer CA2027, che riconosce alcuni pattern inefficienti di timeout basati su Task.WhenAny(..., Task.Delay(...)). Una richiesta completata rapidamente può lasciare vivo fino alla scadenza un timer inutilizzato; WaitAsync elimina quel pattern in molti casi senza cambiare il contratto del lavoro sottostante.
// Evita il Task.Delay che continua a vivere dopo un completamento rapido.
// WaitAsync limita l'attesa: non annulla automaticamente l'operazione.
await operation.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken);
Analisi: su una API ASP.NET Core ad alta concorrenza il guadagno non va cercato soltanto nella latenza media di una funzione, ma nella riduzione di allocazioni per richiesta, pressione di Gen0, timer pendenti, wakeup e saturazione del thread pool. Un benchmark serio mantiene lo stesso carico, lo stesso host e le stesse dipendenze; verifica throughput, allocazioni/op, p95/p99, warm-up e comportamento dopo una collezione di Gen2. Per Native AOT occorre inoltre confrontare startup, memoria residente e impatto di trimming/serialization: i risultati del JIT non si trasferiscono automaticamente ad AOT.
RC2 introduce anche un controllo poco appariscente su HTTP/2
Fra le aggiunte RC2 figura SocketsHttpHandler.InitialHttp2MaxConcurrentStreams, che controlla la concorrenza iniziale degli stream prima che arrivino i SETTINGS del server. È un controllo di protocollo utile nei client molto concorrenti: la fase iniziale di connessione non deve essere confusa con la capacità a regime del servizio. La nuova proprietà non sostituisce né il dimensionamento del pool di connessioni né un limite applicativo sulle richieste in volo.
// Esempio di configurazione disponibile su .NET 11.
var handler = new SocketsHttpHandler
{
InitialHttp2MaxConcurrentStreams = 32,
PooledConnectionLifetime = TimeSpan.FromMinutes(10)
};
using var client = new HttpClient(handler);
Valutazione: per un gateway di inferenza o un aggregatore di feature service il p99 dipende da DNS, TLS, pool, limitazione della concorrenza, backpressure, server remoto e retry. Una proprietà del client risolve un segmento del problema, non la coda end-to-end. La disponibilità dell'API richiede l'SDK/runtime .NET 11: evitare di copiarla in un progetto net10.0 assumendo compatibilità binaria.
GeoAI in ArcGIS: la prossimità semantica non coincide con quella spaziale
Con ArcGIS Pro 3.7 Esri ha introdotto il toolset Embeddings Based Analysis; la guida pubblicata il 16 settembre 2026 chiarisce un aspetto che nei prototipi viene spesso sottovalutato: la scala degli embedding deve essere coerente con la scala della decisione geografica. Il successivo approfondimento Esri del 6 ottobre collega embedding geodemografici, AutoML e SHAP. Non si tratta soltanto di aggiungere 256 colonne numeriche a una feature class: occorre definire che cosa rappresentano e a quale supporto spaziale si riferiscono.
Fonti: Esri — Including Embeddings in Your Spatial Analysis Workflows (16 settembre); Esri — Interpret Embeddings Using AutoML and SHAP (6 ottobre); Esri — ArcGIS Pro 3.7, novità e toolset GeoAI.
Embedding scale, spatial support e perdita d'informazione
I USA Geodemographic Embeddings esaminati da Esri contengono 256 dimensioni, articolate in quattro segmenti di 64 componenti associati a viste differenti: household/housing, ambiente, indicatori ACS e dati demografici. Questi segmenti sono rappresentazioni apprese, non equivalenti a 256 indicatori originali dotati ciascuno di un significato causale. Anche una spiegazione SHAP identifica il contributo di una coordinata latente alla previsione del modello: non dimostra che una variabile demografica abbia causato il fenomeno.
È istruttivo il caso in cui vettori prodotti per celle esagonali siano aggregati su poligoni più grandi. Una media componente per componente è matematicamente possibile, ma può cancellare differenze intra-poligono e non è detto che rimanga sulla stessa manifold appresa dal foundation model. All'opposto, propagare lo stesso vettore di un poligono a tutti i punti al suo interno genera una falsa precisione locale, affine alla ecological fallacy. Esri avverte esplicitamente che aggregare embedding fra scale rimane un'area di ricerca aperta.
Tre operatori, tre domande diverse
GEO proximity d(x, y) < r
Chi si trova realmente entro un raggio di distanza?
SEMANTIC proximity cos(e(x), e(y)) > tau
Quali aree/oggetti hanno caratteristiche latenti simili?
HYBRID retrieval spatial predicate + semantic ranking
Quali candidati geograficamente ammissibili sono simili
al profilo richiesto?
Il ranking ibrido richiede una distinzione fra eligibility e relevance. Per individuare siti idonei a una struttura, i vincoli duri (zona urbanistica, tutela, distanza massima, disponibilità) si applicano prima della classifica semantica. Combinare indiscriminatamente distanza e coseno in un unico punteggio può far emergere un'area semanticamente ottima ma legalmente non utilizzabile.
Quando un punteggio misto è appropriato, una possibile costruzione progettuale è S = alpha × (cos + 1)/2 + (1 - alpha) × exp(-d_km / tau_km) dopo il filtro dei vincoli. Non è un algoritmo Esri ufficiale, né una metrica universale: normalizza il coseno in [0,1] e usa un decadimento spaziale regolabile, ma alpha e tau_km devono essere calibrati sui giudizi del dominio.
using System;
static double Cosine(ReadOnlySpan<float> a, ReadOnlySpan<float> b)
{
if (a.Length != b.Length || a.IsEmpty)
throw new ArgumentException("Vector dimensions mismatch");
double dot = 0, normA = 0, normB = 0;
for (int i = 0; i < a.Length; i++)
{
dot += (double)a[i] * b[i];
normA += (double)a[i] * a[i];
normB += (double)b[i] * b[i];
}
if (normA == 0 || normB == 0)
throw new ArgumentException("Zero vector");
return Math.Clamp(dot / Math.Sqrt(normA * normB), -1, 1);
}
static double HybridScore(
ReadOnlySpan<float> query, ReadOnlySpan<float> candidate,
double distanceKm, double alpha = 0.7, double tauKm = 5.0)
{
if (distanceKm < 0 || alpha < 0 || alpha > 1 || tauKm <= 0)
throw new ArgumentOutOfRangeException();
double semantic = (Cosine(query, candidate) + 1.0) / 2.0;
double geographic = Math.Exp(-distanceKm / tauKm);
return alpha * semantic + (1.0 - alpha) * geographic;
}
// PRECONDIZIONE: candidati gia' filtrati con predicati GIS hard.
// distanceKm deve provenire da un calcolo geodetico/proiettato valido.
// Non usare differenze lon/lat in gradi come se fossero km.
Analisi: in un'applicazione reale il filtro spaziale deve sfruttare gli indici del geodatabase/Feature Service e non scaricare milioni di geometrie per filtrare in memoria. Il calcolo del coseno sopra è intenzionalmente locale e riproducibile: è il reranker su poche centinaia di candidati già selezionati, non il sostituto di un indice vettoriale per milioni di righe. È inoltre indispensabile fissare versione del modello di embedding, definizione delle feature, sistema di riferimento, precisione e data di acquisizione.
Validation: lo spatial leakage produce metriche convincenti ma scorrette
Un train/test split casuale può mettere celle confinanti, fortemente autocorrelate, in entrambi gli insiemi. Il modello sembra generalizzare ma in parte riconosce la geografia di training. Per una previsione utilizzata su nuove aree, il test dovrebbe bloccare porzioni di territorio, mantenere un buffer di separazione o usare gruppi territoriali coerenti con la reale unità di deployment. I confronti significativi sono almeno quattro: baseline tabellare; solo embedding; tabellare + embedding; tabellare + embedding con split geografico e temporale. Misurare anche stabilità delle spiegazioni SHAP, non soltanto R² o F1.
Experience Builder Agent Skills: le convenzioni del framework come vincoli verificabili
La skill per lo sviluppo di widget Experience Builder presentata da Esri il 14 settembre 2026 è disponibile nel repository ufficiale arcgis-experience-builder-sdk-resources. È importante separare il contenuto verificato della skill dalla narrazione secondo cui un agente possa generare qualsiasi widget senza competenze di sviluppo. La documentazione codifica struttura, convenzioni, riferimenti alle API, separazione runtime/settings e pattern di gestione dei layer; non sostituisce compatibilità di versione, build, test e revisione di sicurezza.
Fonti: Esri — Building Custom Experience Builder Widgets with an Agent Skill; Esri SDK resources — repository; Esri Skill — regole ufficiali per map/layer views.
Il punto architetturale: usare ID stabili, non oggetti di runtime persistenti
Le istruzioni della skill richiedono di configurare la selezione della mappa con MapWidgetSelector, registrare gli identificatori in useMapWidgetIds e risolvere le istanze attive con JimuMapViewComponent o i componenti jimu-arcgis. Oggetti JimuMapView e LayerView non vanno serializzati nella configurazione persistente. La view attiva deve essere riacquisita dopo un cambio pagina/mappa e gli handle di reactiveUtils rimossi durante lo switch e l'unmount.
Persistent configuration:
widgetId / dataSourceId / useMapWidgetIds / scalar config
|
v
Runtime resolution:
JimuMapViewComponent -> active JimuMapView
| |
v v
when loaded? acquire layer view / watchers
|
active view changes
|
v
dispose handles / highlights
Questi dettagli sono più importanti di una demo riuscita: le perdite di handle dopo cambi di vista possono produrre callback su mappe dismesse, selezioni inconsistenti e un consumo crescente di memoria. La stessa guida raccomanda di gestire esplicitamente i casi 2d e 3d, lo stato cached/restore della vista, nonché readiness e cleanup delle feature temporanee.
Valutazione: la skill funziona come un capability specification layer per un coding agent, ma resta input testuale controllabile. Un repository compromesso o una modifica non revisionata al file della skill può influenzare il codice prodotto. In una pipeline professionale conviene quindi fissare una revisione Git, sottoporre la skill a code review, mantenere i permessi dell'agente al minimo e considerare vincolanti solo build, test e code review umana. Lo standard documentale riduce gli errori di contesto; non costituisce un sandbox di sicurezza.
Un esperimento verificabile, non una promessa di produttività
La misura utile consiste nel far implementare lo stesso requisito a due esecuzioni dell'agente, una con e una senza skill, mantenendo costanti modello, prompt, stato del repository, tool e versione Experience Builder. Il rubric di valutazione dovrebbe rilevare non solo il successo della compilazione, ma anche gestione del ciclo di vita della mappa, cleanup degli handle, rispetto dei contratti DataSource, accessibilità, eventuali modifiche a file non richiesti e numero di interventi manuali. Un widget che compila ma conserva riferimenti obsoleti dopo una navigazione non è un successo tecnico.
OpenAI: Decisions GA e cache a 12 ore cambiano il costo delle pipeline
Le due novità API più rilevanti della settimana sono la disponibilità della cache dei prompt fino a 12 ore nella Responses API, annunciata il 20 ottobre, e la General Availability della Decisions API, annunciata il 22 ottobre 2026. Sono cambiamenti di contratto e di cost model, più concreti di una generica dichiarazione di miglioramento del modello.
Fonti: OpenAI API Changelog (20–22 ottobre); Decisions API — guida ufficiale; Decisions — riferimento POST /decisions; Prompt caching — funzionamento, TTL, prezzi e privacy.
Decision as a typed endpoint, non come conversazione
POST /v1/decisions, attualmente documentato con il solo modello gpt-6-luna, valuta un input condiviso tramite fino a 400 domande nello stesso request. I tipi sono predicate (probabilità stimata), choice (selezione discreta) e score (valutazione su livelli ordinati); le risposte hanno nomi stabili. Rispetto alla beta sono arrivati riuso del contesto tramite prompt caching, input_json e immagini specifiche per domanda.
Il valore architetturale è separare la fase di valutazione probabilistica da quella di generazione lunga. Un'applicazione può classificare un ticket, verificarne la pertinenza o determinare la necessità di revisione prima di invocare un agente costoso. La definizione dell'output è tipizzata, ma non equivale a una garanzia formale di correttezza. Il produttore descrive l'endpoint come più rapido della Responses API; il fattore di accelerazione presentato nella documentazione è una affermazione del fornitore, non una misura trasferibile senza benchmark sul proprio dataset.
C#/.NET: chiamare Decisions con JSON strutturato
L'esempio seguente usa le API standard di .NET e nessuna libreria client proprietaria: la decisione viene estratta dal JSON di risposta solo dopo controllo HTTP e verifica del tipo. Richiede una chiave API in OPENAI_API_KEY e un account autorizzato all'endpoint.
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
?? throw new InvalidOperationException("Set OPENAI_API_KEY");
using var client = new HttpClient
{
BaseAddress = new Uri("https://api.openai.com/")
};
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", key);
var payload = new
{
model = "gpt-6-luna",
input = new[]
{
new
{
role = "user",
content = new object[]
{
new { type = "input_text",
text = "Evaluate the order evidence, not just the claim." },
new { type = "input_json", data = new
{
ticket = new { body = "The payment was charged twice." },
order = new { charges = new[] { 49, 49 } }
}}
}
}
},
questions = new[]
{
new { type = "predicate", name = "duplicate_charge",
instructions = "Is a duplicate charge supported by the evidence?" }
}
};
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var response = await client.PostAsJsonAsync(
"v1/decisions", payload, cts.Token);
response.EnsureSuccessStatusCode();
using var json = JsonDocument.Parse(
await response.Content.ReadAsStreamAsync(cts.Token));
var answer = json.RootElement.GetProperty("answers")[0];
if (answer.GetProperty("type").GetString() == "predicate")
{
double p = answer.GetProperty("probability").GetDouble();
Console.WriteLine($"Probability: {p:P2}");
}
else
{
Console.WriteLine($"Decision type: {answer.GetProperty("type")}");
}
Il punto non è utilizzare la probabilità come fosse una sentenza: un predicato va calibrato su dati etichettati e soglie distinte per falsi positivi e falsi negativi. Un classificatore che invia alla revisione umana i casi incerti è spesso una scelta più robusta di un generatore che decide autonomamente un'azione irreversibile. Il risultato deve essere registrato con versione di schema, modello, soglia, identificativo della richiesta e outcome umano per costruire un dataset di evaluation.
Cache a 12 ore: costo di scrittura contro frequenza di riuso
Per i modelli supportati dalla nuova API di caching, prompt_cache_options.ttl consente di specificare "30m" o "12h". La documentazione riporta scritture rispettivamente a 1,25× e 2,5× il costo base dei token di input; le letture hanno tariffa scontata secondo il modello. Per molte famiglie recenti il read rate è 0,1× e per gpt-6.1-sol è documentato 0,05×. Non si tratta di uno sconto senza costo iniziale: un prefisso scritto una sola volta e mai riutilizzato può costare di più di un prompt non messo in cache.
{
"model": "gpt-6.1-sol",
"prompt_cache_options": { "mode": "explicit", "ttl": "30m" },
"input": [
{
"role": "developer",
"content": [{
"type": "input_text",
"text": "Stable instructions and reference material ...",
"prompt_cache_breakpoint": { "mode": "explicit", "ttl": "12h" }
}]
},
{ "role": "user", "content": "Dynamic question ..." }
]
}
Per un prefisso stabile con N usi entro il periodo di validità, e senza altre scritture o miss, la spesa in equivalenti di costo-input è approssimativamente C = L × [w + (N - 1) × r], dove L è la lunghezza del prefisso, w il moltiplicatore di scrittura e r quello di lettura. Ad esempio, con una cache a 12 ore e lettura a 0,1×, dieci utilizzi pieni costano L × 3,4 equivalenti contro L × 10 senza caching. È un calcolo teorico, non una stima di fattura: ignora i suffissi dinamici, i token di output, i miss e i costi indipendenti.
Breaking operational change: un prefisso visivamente simile non è necessariamente un prefisso cacheable. Modificare l'ordine dei tool, le istruzioni, il modello, il service tier o il punto di breakpoint può invalidare il match. La documentazione consiglia di utilizzare breakpoint espliciti dopo i blocchi stabili e di leggere usage.input_tokens_details.cached_tokens e cache_write_tokens anziché dedurre l'hit rate dalla latenza. La soglia minima di lunghezza per modelli recenti è documentata a 1.024 token, ma va ricontrollata per modello e configurazione.
Sicurezza e privacy: il prompt caching conserva rappresentazioni KV, non equivale a un archivio del testo del prompt. La documentazione dichiara compatibilità con Zero Data Retention, descrive eventuale stato applicativo cifrato e stabilisce che le cache non attraversano i confini organizzativi né quelli regionali. Il prompt_cache_key separa contabilizzazione e aiuta a prevenire alcuni probing cross-customer, ma non va trattato come un sistema generale di autorizzazione. Nel serving self-hosted valgono confini differenti: la documentazione di sicurezza vLLM avverte che i tenant nello stesso processo condividono risorse e che cache_salt non fornisce isolamento completo.
Fonte aggiuntiva sulla distinzione hosting/tenant: vLLM — security guidance for multi-tenant deployments.
Azure e l'economia fisica del serving AI: VRAM, scheduling e p99
Il caching gestito introduce un trade-off economico per token; nell'inferenza self-hosted su Azure il problema è anche fisico. Il fabbisogno di GPU memory non è la sola dimensione dei pesi del modello: pesi + runtime workspace + KV cache + batch/concorrenza + frammentazione determinano quanti prompt possono realmente convivere. Quantizzare i pesi riduce la prima componente ma non azzera le altre.
La documentazione Microsoft della famiglia NCads H100 v5 indica configurazioni con GPU NVIDIA H100 NVL da 94 GB per dispositivo, oltre a diverse dotazioni di CPU e RAM. Questo dato non implica che un deployment riceva 94 GB interamente utilizzabili per i pesi: CUDA/runtime, allocator e KV cache utilizzano anch'essi memoria. La scelta della SKU deve seguire un dimensionamento in funzione di contesto, numero di richieste attive e p99, non soltanto la possibilità di caricare il checkpoint.
Fonti: Microsoft Learn — Azure NC family; vLLM — Automatic Prefix Caching; vLLM — Security.
Analisi: la CPU può essere adeguata per modelli piccoli, quantizzati, con bassa concorrenza o requisiti di elaborazione batch non interattivi; la GPU diventa cruciale quando throughput e latenza durante prefill/decode sono vincoli di prodotto. L'eventuale risparmio di una VM CPU non è confrontabile direttamente con la tariffa oraria di una VM GPU se il numero di task completati entro SLO è radicalmente diverso. Il criterio utile è (compute + storage + networking + operations) / successful requests within SLO, accompagnato da TTFT, p95/p99, token/sec, picco VRAM e miss rate della cache.
In sistemi con più tenant, il deployment richiede inoltre un vero disegno di isolamento: quote per tenant, rate limiting, policy di retention, rete privata, identificatori non indovinabili e, dove necessario, istanze distinte. Un prompt cache hit è un'ottimizzazione di calcolo; non è un confine di sicurezza.
Technical Takeaways
- Runtime Async è un cambiamento di ownership del lavoro di compilazione. Il nuovo confine compilatore/JIT abilita ottimizzazioni che il lowering tradizionale rendeva difficili, ma richiede misurazione end-to-end e controllo di compatibilità.
- Il GIS semantico deve preservare il supporto spaziale. Stesso embedding non significa stessa posizione, e una metrica coseno alta non sostituisce vincoli territoriali, indici geografici o validation spaziale.
- Agent Skills codificano convenzioni, non garanzie. Il repository ufficiale Esri formalizza regole utili; l'affidabilità deriva dalla catena completa skill revisionata → codice generato → build → test → review.
- Le Decisions tipizzano l'output, non la verità. Probabilità, scelte e scoring entrano in un dominio di policy con soglie, revisione e dataset di valutazione.
- Prompt caching è una decisione di placement economico e sicurezza. TTL, breakpoints, ripetitività del prefisso, tenant boundary e SLO di latenza determinano il beneficio reale.
Lab della settimana
Lab 1 — Misurare Runtime Async senza confondere la misurazione con la feature
Obiettivo: confrontare due build dello stesso progetto net11.0 — con e senza runtime-async=on — usando BenchmarkDotNet per allocazioni/op e tempo medio, e un carico HTTP realistico per p95/p99. Procedura: generare un progetto console, aggiungere BenchmarkDotNet, eseguire lo stesso benchmark che combina chiamate già completate e chiamate con reale sospensione, quindi ripetere a processo fresco e confrontare dimensioni delle DLL. Non utilizzare GC.GetAllocatedBytesForCurrentThread() attraversando await come se fosse una misura affidabile delle allocazioni globali dell'operazione.
dotnet new console -f net11.0 -n RuntimeAsyncLab
cd RuntimeAsyncLab
dotnet add package BenchmarkDotNet
# Run A: senza il feature switch runtime-async nel .csproj.
# Run B: stesso codice e runtime, con runtime-async=on.
dotnet run -c Release
Utilizzare questo Program.cs in entrambe le build, senza modifiche ai sorgenti:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<AsyncHotPath>();
[MemoryDiagnoser]
public class AsyncHotPath
{
[Benchmark]
public async Task<int> CompletedChain() => await Level1(42);
[Benchmark]
public async Task<int> SuspendedChain()
{
await Task.Yield();
return await Level1(42);
}
private static async Task<int> Level1(int x) => await Level2(x);
private static async Task<int> Level2(int x) => await Task.FromResult(x);
}
Documentazione: Microsoft performance deep dive e BenchmarkDotNet overview. Output atteso: matrice di allocazioni, media, distribuzione per modalità sync/async, footprint IL e note sui casi regressivi; nessun numero presunto prima di effettuare i test.
Lab 2 — Spatial-semantic reranking con vincoli geografici
Obiettivo: costruire un set sintetico di 100 feature con coordinate e vettori normalizzati (per evitare dipendenze da licenze), applicare un filtro geografico duro e poi ordinarle usando il metodo C# riportato sopra. Ripetere su una feature class ArcGIS Pro 3.7 utilizzando embedding generati alla stessa scala delle geometrie, confrontando la classifica con un semplice nearest-neighbor geografico. Misure: precision@k rispetto a giudizi di dominio, tempo query, stabilità al variare di alpha e tau; fare uno split per blocchi geografici, non un campionamento casuale.
Per la parte sintetica, dopo le funzioni Cosine e HybridScore, il dataset deterministico seguente rende ripetibile il ranking e dimostra l'ordine filtro → score:
using System;
using System.Linq;
var rng = new Random(42);
float[] q = [1f, 0f, 0f];
var sample = Enumerable.Range(0, 100).Select(i => new
{
Id = i,
Km = rng.NextDouble() * 10,
E = new float[] {
(float)rng.NextDouble(),
(float)rng.NextDouble(),
(float)rng.NextDouble()
}
}).ToArray();
var top10 = sample.Where(x => x.Km <= 5.0)
.OrderByDescending(x => HybridScore(q, x.E, x.Km))
.Take(10);
foreach (var row in top10)
Console.WriteLine($"{row.Id}: {row.Km:F2} km");
// Scala e vettori sono sintetici: non rappresentano dati Esri.
Documentazione: Esri — spatial embeddings e Esri — embedding interpretability.
Lab 3 — Misurare il costo reale del riuso dei prefissi
Obiettivo: inviare richieste Responses API con un blocco di istruzioni realmente stabile e sufficientemente lungo, seguito da suffissi variabili; confrontare 30m e 12h in finestre di riuso compatibili. Registrare costo input stimato, cached_tokens, cache_write_tokens, latenza al primo output e tasso di hit. Separare i run di misura per non attribuire al test successivo i cache hit del precedente; preservare configurazioni di modello e service tier identiche.
Documentazione: OpenAI Prompt Caching, Prompt Cache Diagnostics e OpenAI API Changelog. Controllo: un esperimento privo di hit non va presentato come prova che la cache non funzioni; prima si deve verificare la lunghezza minima, il punto di breakpoint, la configurazione e l'identità effettiva del prefisso.