Perché le stringhe sono così lente?


23

Sin dalla mia prima lezione di programmazione al liceo, ho sentito che le operazioni con le stringhe sono più lente - cioè più costose - della mitica "operazione media". Perché li rende così lenti? (Questa domanda è stata lasciata intenzionalmente ampia.)


11
Se sai che queste "operazioni medie" sono mitiche, puoi almeno dirci quali sono alcune di esse? Dato che stai ponendo una domanda così vaga, è difficile fidarsi della tua affermazione che queste operazioni non specificate sono davvero mitiche.
seh,

1
@seh, sfortunatamente, in realtà non posso rispondere. Le poche volte in cui ho effettivamente chiesto alla gente quali corde sono più lente di quelle, si limitano a scrollare le spalle e dicono "sono solo lente". Inoltre, se avessi informazioni più specifiche, questa sarebbe una domanda per SO, non per i programmatori; è già un po 'borderline.
apre il

Qual è il punto ? Se le stringhe segnalate sono effettivamente lente, smetterai di usarle?
Tulains Córdova,

Dimenticalo. Se qualcuno ti dice una tale assurdità, la controquestione è: "Davvero? Lo sono? Dovremmo usare un int-array allora?"
Ingo,

Risposte:


47

"L'operazione media" ha luogo sui primitivi. Ma anche nei linguaggi in cui le stringhe sono trattate come primitive, sono comunque array sotto il cofano, e fare qualsiasi cosa che coinvolga l'intera stringa richiede tempo O (N), dove N è la lunghezza della stringa.

Ad esempio, l'aggiunta di due numeri richiede generalmente 2-4 istruzioni ASM. La concatenazione ("aggiunta") di due stringhe richiede una nuova allocazione di memoria e una o due copie di stringa, coinvolgendo l'intera stringa.

Alcuni fattori linguistici possono peggiorare le cose. In C, ad esempio, una stringa è semplicemente un puntatore a un array di caratteri con terminazione null. Ciò significa che non sai quanto è lungo, quindi non c'è modo di ottimizzare un ciclo di copia delle stringhe con operazioni di spostamento rapido; è necessario copiare un carattere alla volta in modo da poter testare ogni byte per il terminatore null.


4
E alcuni linguaggi lo rendono molto migliore: la codifica di Delphi della lunghezza della stringa all'inizio dell'array rende la concatenazione di stringhe molto veloce.
Frank Shearar,

4
@gablin: aiuta anche facendo in modo che la stringa si copi molto più velocemente. Quando si conosce la dimensione in primo piano, non è necessario copiare un byte alla volta e controllare ogni byte per un terminatore nullo, quindi è possibile utilizzare l'intera dimensione di qualsiasi registro, inclusi quelli SIMD, per lo spostamento dei dati, rendendo fino a 16 volte più veloce.
Mason Wheeler,

4
@mathepic: Sì, e va bene per quanto ti porterà, ma quando inizi a interagire con libc o altro codice esterno, si aspetta un char*, non un strbuf, e sei di nuovo al punto 1. C'è solo così tanto può fare quando un cattivo design è inserito nella lingua.
Mason Wheeler,

6
@mathepic: ovviamente il bufpuntatore è lì. Non ho mai voluto sottintendere che non sia disponibile; piuttosto, che è necessario. Qualsiasi codice che non conosca il tipo di stringa ottimizzato ma non standard, comprese le cose fondamentali come la libreria standard , deve ancora ricorrere al lento, non sicuro char*. Puoi chiamare quel FUD se vuoi, ma questo non lo rende non vero.
Mason Wheeler,

7
Gente, c'è una colonna di Joel Spolsky sul punto di Frank Shearer: Back to Basics
user16764,

14

Questo è un vecchio thread e penso che le altre risposte siano fantastiche, ma trascurano qualcosa, quindi ecco i miei (in ritardo) 2 centesimi.

Il rivestimento sintetico di zucchero nasconde la complessità

Il problema con le stringhe è che sono cittadini di seconda classe nella maggior parte delle lingue, e in realtà la maggior parte delle volte non fanno realmente parte delle specifiche della lingua stessa: sono un costrutto implementato in biblioteca con qualche occasionale rivestimento sintattico di zucchero nella parte superiore per renderli meno dolorosi da usare.

La conseguenza diretta di ciò è che il linguaggio nasconde gran parte della loro complessità lontano dalla tua vista e paghi per gli effetti collaterali subdoli perché prendi l'abitudine di considerarli come un'entità atomica di basso livello, proprio come altri tipi primitivi (come spiegato dalla risposta più votata e altri).

Dettagli di implementazione

Good Ol 'Array

Uno degli elementi di questa "complessità" sottostante è che la maggior parte delle implementazioni di stringhe ricorrerebbe all'uso di una semplice struttura di dati con uno spazio di memoria contiguo per rappresentare la stringa: il tuo buon vecchio array.

Questo ha senso, intendiamoci, poiché vuoi che l'accesso alla stringa nel suo insieme sia veloce. Ciò implica costi potenzialmente terribili quando si desidera manipolare questa stringa. Accedere a un elemento nel mezzo potrebbe essere veloce se sai quale indice stai cercando , ma cercare un elemento basato su una condizione non lo è.

Anche restituire la dimensione della stringa potrebbe essere costoso, se la tua lingua non memorizza nella cache la lunghezza della stringa e deve attraversarla per contare i caratteri.

Per motivi analoghi, l' aggiunta di elementi alla stringa risulterà costosa poiché molto probabilmente dovrai riassegnare un po 'di memoria perché l'operazione si verifichi.

Quindi, lingue diverse adottano approcci diversi a questi problemi. Java, ad esempio, si è preso la libertà di rendere immutabili le sue stringhe per alcuni motivi validi (lunghezza della cache, sicurezza dei thread) e per le sue controparti mutabili (StringBuffer e StringBuilder) sceglierà di allocare le dimensioni usando blocchi di dimensioni maggiori per non dover allocare ogni volta, ma piuttosto spero per gli scenari migliori. In genere funziona bene, ma il lato negativo è a volte pagare per gli impatti della memoria.

Supporto Unicode

Inoltre, e questo è dovuto al fatto che il rivestimento sintattico di zucchero della tua lingua ti nasconde questo per giocare bene, spesso non pensi che termini di supporto Unicode (specialmente finché non ne hai davvero bisogno e colpire quel muro). E alcune lingue, essendo lungimiranti, non implementano stringhe con matrici sottostanti di semplici primitivi char a 8 bit. Hanno funzionato in UTF-8 o UTF-16 o supporto what-have-you per te e la conseguenza è un consumo di memoria tremendamente maggiore, che spesso non è necessario, e un tempo di elaborazione maggiore per allocare memoria, elaborare le stringhe, e implementare tutta la logica che va di pari passo con la manipolazione dei punti di codice.


Il risultato di tutto ciò è che quando fai qualcosa di equivalente in pseudo-codice a:

hello = "hello,"
world = " world!"
str = hello + world

Potrebbe non essere - nonostante tutti i migliori sforzi che gli sviluppatori del linguaggio hanno fatto per farli comportare come avresti fatto - semplice come:

a = 1;
b = 2;
shouldBeThree = a + b

Come follow-up, potresti voler leggere:


Buona aggiunta alla discussione attuale.
Abele

Ho appena capito che questa è la risposta migliore perché l'affermazione mitica può essere applicata a qualcosa come la crittografia RSA è lenta. L'unico motivo per cui la stringa viene inserita in questo punto imbarazzante è perché l'operatore plus ha fornito le stringhe nella maggior parte delle lingue, il che rende i neofiti non consapevoli del costo dietro l'operazione.
Codismo,

@Abel: grazie, mi è sembrato lo spazio per dettagli più generici.
Hayylem,

@Codismo: grazie, felice che ti sia piaciuta. Penso davvero che questo possa essere applicato a molti casi in cui è nascosta solo una questione di complessità (e di noi non prestiamo più così tanta attenzione ai dettagli di livello inferiore fino a quando non ne avremo finalmente bisogno perché colpiamo un collo di bottiglia o un muro di mattoni di qualche tipo ).
Hayylem,

1

La frase "operazione media" è probabilmente una scorciatoia per una singola operazione di una macchina teorica ad accesso casuale programmata . Questa è la macchina teorica che è consuetudine utilizzare per analizzare il tempo di esecuzione di vari algoritmi.

Le operazioni generiche sono normalmente considerate caricamento, aggiunta, sottrazione, memorizzazione, diramazione. Forse anche leggere, stampare e fermare.

Ma la maggior parte delle operazioni sulle stringhe richiedono molte di queste operazioni fondamentali. Ad esempio, la duplicazione di una stringa richiede normalmente un'operazione di copia, e quindi un numero di operazioni proporzionale alla lunghezza di una stringa (ovvero, è "lineare"). Anche trovare una sottostringa all'interno di un'altra stringa ha una complessità lineare.


1

Dipende completamente dall'operazione, da come sono rappresentate le stringhe e da quali ottimizzazioni esistono. Se le stringhe sono lunghe 4 o 8 byte (e allineate), non sarebbero necessariamente più lente - molte operazioni sarebbero altrettanto veloci delle primitive. Oppure, se tutte le stringhe hanno un hash a 32 o 64 bit, molte operazioni sarebbero altrettanto veloci (anche se si paga in anticipo il costo di hashing).

Dipende anche da cosa intendi per "lento". La maggior parte dei programmi elaborerà le stringhe molto velocemente per ciò che è necessario. Il confronto delle stringhe potrebbe non essere veloce come il confronto di due ints, ma solo la profilatura rivelerà cosa significa "lento" per il tuo programma.


0

Lasciami rispondere alla tua domanda con una domanda. Perché pronunciare una stringa di parole richiede più tempo rispetto a pronunciare una sola parola?


2
Non necessariamente.
user16764,

3
Supercalifragilisticexpialidocious
Spoike

s / word / sillaba / g
Caleb

Lasciami rispondere alla tua domanda-risposta con una domanda: perché non dici cosa significa la tua risposta? Dopotutto, è tutt'altro che chiaro come possa essere interpretato come applicabile ad un sistema runtime.
PJTraill,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.