Programmazione funzionale: l'immutabilità è costosa? [chiuso]


98

La domanda è divisa in due parti. Il primo è concettuale. Il prossimo guarda più concretamente alla stessa domanda in Scala.

  1. Utilizzare solo strutture di dati immutabili in un linguaggio di programmazione rende l'implementazione di determinati algoritmi / logica intrinsecamente più costosa dal punto di vista computazionale nella pratica? Ciò attinge al fatto che l'immutabilità è un principio fondamentale dei linguaggi puramente funzionali. Ci sono altri fattori che influiscono su questo?
  2. Facciamo un esempio più concreto. Quicksort viene generalmente insegnato e implementato utilizzando operazioni mutabili su una struttura dati in memoria. Come si implementa una cosa del genere in modo funzionale PURO con un sovraccarico di calcolo e archiviazione comparabile alla versione modificabile. Nello specifico in Scala. Ho incluso alcuni benchmark grezzi di seguito.

Più dettagli:

Vengo da un background di programmazione imperativo (C ++, Java). Ho esplorato la programmazione funzionale, in particolare Scala.

Alcuni dei principi primari della pura programmazione funzionale:

  1. Le funzioni sono cittadini di prima classe.
  2. Le funzioni non hanno effetti collaterali e quindi gli oggetti / le strutture dati sono immutabili .

Anche se le JVM moderne sono estremamente efficienti con la creazione di oggetti e la garbage collection è molto poco costosa per oggetti di breve durata, è probabilmente ancora meglio ridurre al minimo la creazione di oggetti, giusto? Almeno in un'applicazione a thread singolo in cui la concorrenza e il blocco non sono un problema. Poiché Scala è un paradigma ibrido, si può scegliere di scrivere codice imperativo con oggetti mutabili, se necessario. Ma, come qualcuno che ha trascorso molti anni cercando di riutilizzare gli oggetti e ridurre al minimo l'allocazione. Vorrei una buona comprensione della scuola di pensiero che non lo permetterebbe nemmeno.

Come caso specifico, sono rimasto un po 'sorpreso da questo frammento di codice in questo tutorial 6 . Ha una versione Java di Quicksort seguita da un'attenta implementazione Scala dello stesso.

Ecco il mio tentativo di confrontare le implementazioni. Non ho eseguito una profilazione dettagliata. Ma la mia ipotesi è che la versione Scala sia più lenta perché il numero di oggetti allocati è lineare (uno per chiamata di ricorsione). C'è qualche possibilità che le ottimizzazioni della chiamata di coda possano entrare in gioco? Se ho ragione, Scala supporta l'ottimizzazione delle chiamate tail per le chiamate auto-ricorsive. Quindi, dovrebbe solo aiutarlo. Sto usando Scala 2.8.

Versione Java

public class QuickSortJ {

    public static void sort(int[] xs) {
      sort(xs, 0, xs.length -1 );
    }

    static void sort(int[] xs, int l, int r) {
      if (r >= l) return;
      int pivot = xs[l];
      int a = l; int b = r;
      while (a <= b){
        while (xs[a] <= pivot) a++;
        while (xs[b] > pivot) b--;
        if (a < b) swap(xs, a, b);
      }
      sort(xs, l, b);
      sort(xs, a, r);
    }

    static void swap(int[] arr, int i, int j) {
      int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
    }
}

Versione Scala

object QuickSortS {

  def sort(xs: Array[Int]): Array[Int] =
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length / 2)
      Array.concat(
        sort(xs filter (pivot >)),
        xs filter (pivot ==),
        sort(xs filter (pivot <)))
    }
}

Scala Code per confrontare le implementazioni

import java.util.Date
import scala.testing.Benchmark

class BenchSort(sortfn: (Array[Int]) => Unit, name:String) extends Benchmark {

  val ints = new Array[Int](100000);

  override def prefix = name
  override def setUp = {
    val ran = new java.util.Random(5);
    for (i <- 0 to ints.length - 1)
      ints(i) = ran.nextInt();
  }
  override def run = sortfn(ints)
}

val benchImmut = new BenchSort( QuickSortS.sort , "Immutable/Functional/Scala" )
val benchMut   = new BenchSort( QuickSortJ.sort , "Mutable/Imperative/Java   " )

benchImmut.main( Array("5"))
benchMut.main( Array("5"))

Risultati

Tempo in millisecondi per cinque esecuzioni consecutive

Immutable/Functional/Scala    467    178    184    187    183
Mutable/Imperative/Java        51     14     12     12     12

10
È costoso se implementato in modo ingenuo o con metodi sviluppati per linguaggi imperativi. Un compilatore intelligente (ad esempio GHC, un compilatore Haskell - e Haskell ha solo valori immutabili) può trarre vantaggio dall'immutabilità e dalle prestazioni dell'archivio che possono competere con il codice che utilizza la mutabilità. Inutile dire che l'implementazione ingenua di quicksort è ancora lenta perché utilizza, tra le altre cose costose, la ricorsione pesante e la O(n)concatenazione di elenchi. Tuttavia, è più breve della versione con pseudocodice;)

3
Un ottimo articolo del blog correlato è qui: blogs.sun.com/jrose/entry/larval_objects_in_the_vm incoraggiarlo, perché questo gioverebbe notevolmente a Java e ai linguaggi VM funzionali
andersoj

2
questo thread SO ha molte discussioni dettagliate sull'efficienza della programmazione funzionale. stackoverflow.com/questions/1990464/… . Risponde a molte cose che volevo sapere.
smartnut007

5
La cosa più ingenua qui è il tuo punto di riferimento. Non puoi confrontare nulla con un codice del genere! Dovresti leggere seriamente alcuni articoli sull'esecuzione di benchmark sulla JVM prima di trarre conclusioni ... lo sapevi che la JVM potrebbe non aver ancora eseguito il JIT del codice prima di eseguirla? Hai impostato la dimensione iniziale e massima dell'heap in modo appropriato (in modo da non considerare il tempo in cui i processi JVM richiedono più memoria?)? Sei a conoscenza di quali metodi vengono compilati o ricompilati? Conosci GC? I risultati che ottieni da questo codice non significano assolutamente nulla!
Bruno Reis

2
@userunknown No, è dichiarativo. La programmazione imperativa "cambia stato con i comandi" mentre la programmazione funzionale "è un paradigma di programmazione dichiarativa" che "evita di cambiare stato" ( Wikipedia ). Quindi, sì, funzionale e imperativo sono due cose completamente diverse e il codice che hai scritto non è imperativo.
Brian McCutchon

Risposte:


106

Poiché ci sono alcune idee sbagliate che volano qui intorno, vorrei chiarire alcuni punti.

  • Il quicksort "sul posto" non è realmente sul posto (e il quicksort non è per definizione sul posto). Richiede memoria aggiuntiva sotto forma di spazio stack per il passaggio ricorsivo, che è nell'ordine O (log n ) nel migliore dei casi, ma O ( n ) nel caso peggiore.

  • L'implementazione di una variante funzionale di quicksort che opera sugli array vanifica lo scopo. Gli array non sono mai immutabili.

  • L'implementazione funzionale "corretta" di quicksort utilizza elenchi immutabili. Ovviamente non è sul posto ma ha lo stesso runtime asintotico nel caso peggiore ( O ( n ^ 2)) e complessità spaziale ( O ( n )) come la versione procedurale sul posto.

    In media, il suo tempo di esecuzione è ancora alla pari con quello della variante sul posto ( O ( n log n )). La sua complessità spaziale, tuttavia, è ancora O ( n ).


Ci sono due ovvi svantaggi di un'implementazione di Quicksort funzionale. Di seguito, consideriamo questa implementazione di riferimento in Haskell (non conosco Scala ...) dall'introduzione di Haskell :

qsort []     = []
qsort (x:xs) = qsort lesser ++ [x] ++ qsort greater
    where lesser  = (filter (< x) xs)
          greater = (filter (>= x) xs)
  1. Il primo svantaggio è la scelta dell'elemento a perno , che è molto flessibile. La forza delle moderne implementazioni quicksort si basa in gran parte su una scelta intelligente del pivot (confrontare "Engineering a sort function" di Bentley et al. ). L'algoritmo di cui sopra è scadente a tale riguardo, il che degrada notevolmente le prestazioni medie.

  2. In secondo luogo, questo algoritmo utilizza la concatenazione di liste (invece della costruzione di liste) che è un'operazione O ( n ). Ciò non influisce sulla complessità asintotica ma è un fattore misurabile.

Un terzo svantaggio è in qualche modo nascosto: a differenza della variante "sul posto", questa implementazione richiede continuamente memoria dall'heap per le celle contro dell'elenco e potenzialmente disperde la memoria dappertutto. Di conseguenza, questo algoritmo ha una località cache molto scarsa . Non so se gli allocatori intelligenti nei moderni linguaggi di programmazione funzionale possano mitigare questo problema, ma sulle macchine moderne, i problemi di cache sono diventati un importante killer delle prestazioni.


Qual è la conclusione? A differenza di altri, non direi che il quicksort sia intrinsecamente imperativo ed è per questo che si comporta male in un ambiente FP. Al contrario, direi che quicksort è un perfetto esempio di algoritmo funzionale: si traduce perfettamente in un ambiente immutabile, il suo tempo di esecuzione asintotico e la complessità dello spazio sono alla pari con l'implementazione procedurale, e anche la sua implementazione procedurale impiega la ricorsione.

Ma questo algoritmo funziona ancora peggio quando è vincolato a un dominio immutabile. La ragione di ciò è che l'algoritmo ha la peculiare proprietà di beneficiare di molte regolazioni fini (a volte di basso livello) che possono essere eseguite in modo efficiente solo sugli array. Una descrizione ingenua del quicksort sfugge a tutte queste complessità (sia nella variante funzionale che in quella procedurale).

Dopo aver letto "Progettazione di una funzione di ordinamento" non posso più considerare quicksort un algoritmo elegante. Implementato in modo efficiente, è un pasticcio goffo, il lavoro di un ingegnere, non di un artista (per non svalutare l'ingegneria! Questo ha una sua estetica).


Ma vorrei anche sottolineare che questo punto è particolare per Quicksort. Non tutti gli algoritmi sono suscettibili dello stesso tipo di aggiustamento di basso livello. Molti algoritmi e strutture dati possono davvero essere espressi senza perdita di prestazioni in un ambiente immutabile.

E l'immutabilità può persino ridurre i costi delle prestazioni eliminando la necessità di copie costose o sincronizzazioni cross-thread.

Quindi, per rispondere alla domanda iniziale, “l' immutabilità è costosa? ”- Nel caso particolare del quicksort, c'è un costo che è effettivamente il risultato dell'immutabilità. Ma in generale no .


10
+1 - ottima risposta! Anche se personalmente avrei finito con a volte piuttosto che con no . Tuttavia, è solo personalità: hai spiegato molto bene i problemi.
Rex Kerr

6
Dovresti aggiungere che un'implementazione corretta che utilizza valori immutabili è immediatamente parallelizzabile rispetto alle versioni imperative. Nel contesto tecnologico moderno, questo diventa sempre più importante.
Raphael

Quanto costerebbe usare l' qsort lesser ++ (x : qsort greater)aiuto?
Solomon Ucko,

42

Ci sono un sacco di cose sbagliate in questo come punto di riferimento della programmazione funzionale. I punti salienti includono:

  • Stai usando primitive, che potrebbero dover essere inscatolate / unboxed. Non stai cercando di testare il sovraccarico di avvolgere oggetti primitivi, stai cercando di testare l'immutabilità.
  • Hai scelto un algoritmo in cui l'operazione sul posto è insolitamente efficace (e lo è dimostrato). Se vuoi dimostrare che esistono algoritmi più veloci se implementati in modo mutevole, questa è una buona scelta; in caso contrario, è probabile che sia fuorviante.
  • Stai usando la funzione di temporizzazione sbagliata. Usa System.nanoTime.
  • Il benchmark è troppo breve per essere sicuro che la compilazione JIT non sarà una parte significativa del tempo misurato.
  • L'array non è suddiviso in modo efficiente.
  • Gli array sono mutabili, quindi usarli con FP è comunque uno strano confronto.

Quindi, questo confronto è un ottimo esempio del fatto che devi comprendere il tuo linguaggio (e l'algoritmo) in dettaglio per scrivere codice ad alte prestazioni. Ma non è un ottimo confronto tra FP e non FP. Se lo desideri, dai un'occhiata a Haskell vs. C ++ al Computer Languages ​​Benchmark Game . Il messaggio da portare a casa è che la penalità non è in genere più di un fattore di 2 o 3 o giù di lì, ma dipende davvero. (Nessuna promessa che la gente di Haskell abbia scritto gli algoritmi più veloci possibili, ma almeno alcuni di loro probabilmente hanno provato! Poi di nuovo, alcuni degli Haskell chiamano le librerie C ...)

Supponiamo ora di volere un benchmark più ragionevole di Quicksort, riconoscendo che questo è probabilmente uno dei casi peggiori per FP vs algoritmi mutabili e ignorando il problema della struttura dei dati (cioè fingendo di poter avere un array immutabile):

object QSortExample {
  // Imperative mutable quicksort
  def swap(xs: Array[String])(a: Int, b: Int) {
    val t = xs(a); xs(a) = xs(b); xs(b) = t
  }
  def muQSort(xs: Array[String])(l: Int = 0, r: Int = xs.length-1) {
    val pivot = xs((l+r)/2)
    var a = l
    var b = r
    while (a <= b) {
      while (xs(a) < pivot) a += 1
      while (xs(b) > pivot) b -= 1
      if (a <= b) {
        swap(xs)(a,b)
        a += 1
        b -= 1
      }
    }
    if (l<b) muQSort(xs)(l, b)
    if (b<r) muQSort(xs)(a, r)
  }

  // Functional quicksort
  def fpSort(xs: Array[String]): Array[String] = {
    if (xs.length <= 1) xs
    else {
      val pivot = xs(xs.length/2)
      val (small,big) = xs.partition(_ < pivot)
      if (small.length == 0) {
        val (bigger,same) = big.partition(_ > pivot)
        same ++ fpSort(bigger)
      }
      else fpSort(small) ++ fpSort(big)
    }
  }

  // Utility function to repeat something n times
  def repeat[A](n: Int, f: => A): A = {
    if (n <= 1) f else { f; repeat(n-1,f) }
  }

  // This runs the benchmark
  def bench(n: Int, xs: Array[String], silent: Boolean = false) {
    // Utility to report how long something took
    def ptime[A](f: => A) = {
      val t0 = System.nanoTime
      val ans = f
      if (!silent) printf("elapsed: %.3f sec\n",(System.nanoTime-t0)*1e-9)
      ans
    }

    if (!silent) print("Scala builtin ")
    ptime { repeat(n, {
      val ys = xs.clone
      ys.sorted
    }) }
    if (!silent) print("Mutable ")
    ptime { repeat(n, {
      val ys = xs.clone
      muQSort(ys)()
      ys
    }) }
    if (!silent) print("Immutable ")
    ptime { repeat(n, {
      fpSort(xs)
    }) }
  }

  def main(args: Array[String]) {
    val letters = (1 to 500000).map(_ => scala.util.Random.nextPrintableChar)
    val unsorted = letters.grouped(5).map(_.mkString).toList.toArray

    repeat(3,bench(1,unsorted,silent=true))  // Warmup
    repeat(3,bench(10,unsorted))     // Actual benchmark
  }
}

Notare la modifica al Quicksort funzionale in modo che possa esaminare i dati solo una volta, se possibile, e il confronto con l'ordinamento integrato. Quando lo eseguiamo otteniamo qualcosa come:

Scala builtin elapsed: 0.349 sec
Mutable elapsed: 0.445 sec
Immutable elapsed: 1.373 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.441 sec
Immutable elapsed: 1.374 sec
Scala builtin elapsed: 0.343 sec
Mutable elapsed: 0.442 sec
Immutable elapsed: 1.383 sec

Quindi, oltre a imparare che provare a scrivere il tuo tipo è una cattiva idea, troviamo che c'è una penalità di ~ 3x per un quicksort immutabile se quest'ultimo viene implementato con una certa attenzione. (Potresti anche scrivere un metodo trisect che restituisca tre array: quelli minori di, quelli uguali e quelli maggiori del pivot. Ciò potrebbe accelerare leggermente di più.)


Solo per quanto riguarda la boxe / unboxing. Semmai questo dovrebbe essere una penalità sul lato Java, giusto? Non è Int il tipo numerico preferito per Scala (rispetto a Integer). Quindi, non c'è boxe che sta accadendo sul lato scala. La boxe è un problema solo dal lato java perché l'autoboxing forma scala Int in java.lang.Integer / int. ecco un link che parla in dettaglio di questo argomento ansorg-it.com/en/scalanews-001.html
smartnut007

Sì, qui interpreto l'avvocato del diavolo. La mutevolezza è una parte integrante del design dei quicksorts. Ecco perché ero molto curioso del puro approccio funzionale al problema. Sigh, ho detto questa affermazione la decima volta sul thread :-). Guarderò il resto del tuo post quando mi sveglio e torno. Grazie.
smartnut007

2
@ smartnut007 - Le collezioni Scala sono generiche. I generici richiedono i tipi in scatola per la maggior parte (sebbene sia in corso uno sforzo per specializzarli per alcuni tipi primitivi). Quindi non puoi usare tutti i metodi di raccolta ingegnosi e presumere che non ci saranno penalità quando passerai raccolte di tipi primitivi attraverso di essi. È abbastanza probabile che il tipo primitivo debba essere inscatolato all'entrata e unboxed all'uscita.
Rex Kerr

Non mi piace il fatto che il difetto principale che hai dichiarato sia solo una speculazione :-)
smartnut007

1
@ smartnut007 - È un difetto principale perché è difficile da controllare e se è vero rovina davvero i risultati. Se sei certo che non ci sia boxe, allora sono d'accordo che il difetto non sia valido. Il difetto non è che ci sia la boxe, è che non sai se c'è la boxe (e non ne sono sicuro neanche io - la specializzazione ha reso questo difficile da capire). Sul lato Java (o l'implementazione mutabile di Scala) non c'è boxing perché usi solo primitive. Ad ogni modo, una versione immutabile funziona attraverso n log n spazio, quindi finisci davvero per confrontare il costo di confronto / scambio con l'allocazione di memoria.
Rex Kerr

10

Non penso che la versione Scala sia effettivamente ricorsiva in coda, dal momento che stai usando Array.concat.

Inoltre, solo perché questo è un codice Scala idiomatico, ciò non significa che sia il modo migliore per farlo.

Il modo migliore per farlo sarebbe usare una delle funzioni di ordinamento incorporate in Scala. In questo modo ottieni la garanzia di immutabilità e sai di avere un algoritmo veloce.

Vedi la domanda Stack Overflow Come ordino un array in Scala? per un esempio.


4
inoltre, non penso che sia possibile un ordinamento rapido ricorsivo della coda, poiché devi effettuare due chiamate ricorsive
Alex Lo

1
È possibile, devi solo usare le chiusure di continuazione per sollevare i tuoi potenziali stack frame sul mucchio.
Brian

scala.util.Sorting.quickSort (array) integrato modifica l'array. Funziona velocemente come Java, non sorprendentemente. Sono interessato a una soluzione funzionale pura efficiente. In caso contrario, perché. È una limitazione di Scala o del paradigma funzionale in generale. quella cosa sorta.
smartnut007

@ smartnut007: Quale versione di Scala stai usando? In Scala 2.8, puoi fare ciò array.sortedche restituisce un nuovo array ordinato, non muta quello originale.
Missingfaktor

@AlexLo - è possibile un ordinamento rapido ricorsivo della coda. Qualcosa del tipo:TAIL-RECURSIVE-QUICKSORT(Array A, int lo, int hi): while p < r: q = PARTITION(A, lo, hi); TAIL-RECURSIVE-QUICKSORT(A, lo, q - 1); p = q + 1;
Jakeway

8

L'immutabilità non è costosa. Sicuramente può essere costoso misurare un piccolo sottoinsieme delle attività che un programma deve svolgere e scegliere una soluzione basata sulla mutevolezza per l'avvio, come misurare il quicksort.

Per dirla semplicemente, non si ordina rapidamente quando si utilizzano linguaggi puramente funzionali.

Consideriamolo da un'altra angolazione. Consideriamo queste due funzioni:

// Version using mutable data structures
def tailFrom[T : ClassManifest](arr: Array[T], p: T => Boolean): Array[T] = {
  def posIndex(i: Int): Int = {
    if (i < arr.length) {
      if (p(arr(i)))
        i
      else
        posIndex(i + 1)
    } else {
      -1
    }
  }

  var index = posIndex(0)

  if (index < 0) Array.empty
  else {
    var result = new Array[T](arr.length - index)
    Array.copy(arr, index, result, 0, arr.length - index)
    result
  }
}

// Immutable data structure:

def tailFrom[T](list: List[T], p: T => Boolean): List[T] = {
  def recurse(sublist: List[T]): List[T] = {
    if (sublist.isEmpty) sublist
    else if (p(sublist.head)) sublist
    else recurse(sublist.tail)
  }
  recurse(list)
}

Benchmark QUELLO e scoprirai che il codice che utilizza strutture dati mutabili ha prestazioni molto peggiori, perché ha bisogno di copiare l'array, mentre il codice immutabile non deve preoccuparsi di questo.

Quando si programma con strutture di dati immutabili, si struttura il codice in modo da sfruttare i suoi punti di forza. Non è semplicemente il tipo di dati o anche i singoli algoritmi. Il programma sarà progettato in modo diverso.

Ecco perché il benchmarking di solito non ha senso. O scegli algoritmi che sono naturali per uno stile o per un altro, e quello stile vince, o fai il benchmark dell'intera applicazione, il che è spesso poco pratico.


7

Ordinare un array è, come, il compito più imperativo nell'universo. Non sorprende che molte eleganti strategie / implementazioni "immutabili" falliscano male su un microbenchmark "sort an array". Ciò non significa che l'immutabilità sia costosa "in generale", però. Ci sono molte attività in cui le implementazioni immutabili funzioneranno in modo paragonabile a quelle modificabili, ma l'ordinamento di array spesso non è uno di questi.


7

Se stai semplicemente riscrivendo i tuoi algoritmi imperativi e le strutture dati in un linguaggio funzionale, sarà davvero costoso e inutile. Per far brillare le cose, dovresti usare le funzionalità disponibili solo nella programmazione funzionale: persistenza delle strutture dei dati, valutazioni pigre ecc.


potresti essere così gentile da fornire un'implementazione in Scala.
smartnut007

3
powells.com/biblio/17-0521631246-0 (Purely Functional Data Structures di Chris Okasaki): basta guardare questo libro. Ha una storia forte da raccontare sullo sfruttamento dei vantaggi della programmazione funzionale, quando si implementano algoritmi e strutture dati efficaci.
Vasil Remeniuk

1
code.google.com/p/pfds alcune strutture dati implementate in Scala da Debashish Ghosh
Vasil Remeniuk

Puoi spiegare perché pensi che Scala non sia un imperativo? list.filter (foo).sort (bar).take (10)- cosa potrebbe essere più imperativo?
utente sconosciuto

7

Il costo dell'immutabilità in Scala

Ecco una versione che è quasi altrettanto veloce di quella Java. ;)

object QuickSortS {
  def sort(xs: Array[Int]): Array[Int] = {
    val res = new Array[Int](xs.size)
    xs.copyToArray(res)
    (new QuickSortJ).sort(res)
    res
  }
}

Questa versione crea una copia dell'array, lo ordina in posizione utilizzando la versione Java e restituisce la copia. Scala non ti obbliga a utilizzare internamente la struttura immutabile.

Quindi il vantaggio di Scala è che puoi sfruttare la mutabilità e l'immutabilità come meglio credi. Lo svantaggio è che se sbagli non ottieni realmente i benefici dell'immutabilità.


Sebbene questa non sia una risposta precisa alla domanda, penso che faccia parte di qualsiasi buona risposta: Quicksort è più veloce quando si utilizza una struttura mutevole. Ma il vantaggio principale dell'immutabilità è l'interfaccia, e almeno in Scala puoi avere entrambi. La mutabilità è più veloce per il quicksort, ma questo non ostacola il tuo modo di scrivere codice performante, per lo più immutabile.
Paul Draper

7

QuickSort è noto per essere più veloce se eseguito sul posto, quindi non è certo un confronto equo!

Detto questo ... Array.concat? Se non altro, stai mostrando come un tipo di raccolta ottimizzato per la programmazione imperativa sia particolarmente lento quando provi a usarlo in un algoritmo funzionale; quasi ogni altra scelta sarebbe più veloce!


Un altro punto molto importante da considerare, forse il problema più importante quando si confrontano i due approcci è: "quanto bene si ridimensiona a più nodi / core?"

È probabile che, se stai cercando un quicksort immutabile, lo stai facendo perché in realtà vuoi un quicksort parallelo. Wikipedia ha alcune citazioni su questo argomento: http://en.wikipedia.org/wiki/Quicksort#Parallelizations

La versione scala può semplicemente fare il fork prima che la funzione ricorra, permettendole di ordinare molto rapidamente un elenco contenente miliardi di voci se hai abbastanza core disponibili.

In questo momento, la GPU del mio sistema ha 128 core a mia disposizione se solo potessi eseguire il codice Scala su di essa, e questo è su un semplice sistema desktop due anni indietro rispetto alla generazione attuale.

Come potrebbe combaciare con l'approccio imperativo a thread singolo, mi chiedo ...

Forse la domanda più importante è quindi:

"Dato che i singoli core non diventeranno più veloci e la sincronizzazione / blocco rappresenta una vera sfida per la parallelizzazione, la mutabilità è costosa?"


Nessun argomento lì. Quicksort è per definizione un ordinamento in memoria. Sono sicuro che la maggior parte delle persone lo ricorda dal college. Ma come fai a smistare in modo puramente funzionale. cioè senza effetti collaterali.
smartnut007

La sua causa importante, ci sono affermazioni che il paradigma funzionale può essere altrettanto veloce delle funzioni con effetti collaterali.
smartnut007

La versione elenco riduce i tempi della metà. Tuttavia, non è neanche lontanamente vicino alla velocità della versione java.
smartnut007

Puoi spiegare perché pensi che Scala non sia un imperativo? list.filter (foo).sort (bar).take (10)- cosa potrebbe essere più imperativo? Grazie.
utente sconosciuto

@ utente sconosciuto: forse potresti chiarire cosa pensi significhi "imperativo", perché il tuo esempio dichiarato mi sembra ben funzionale. Scala stessa non è né imperativa né dichiarativa, il linguaggio supporta entrambi gli stili e questi termini sono usati al meglio per descrivere esempi specifici.
Kevin Wright

2

È stato detto che la programmazione OO utilizza l'astrazione per nascondere la complessità e la programmazione funzionale utilizza l'immutabilità per rimuovere la complessità. Nel mondo ibrido di Scala possiamo usare OO per nascondere il codice imperativo senza lasciare il codice dell'applicazione più saggio. In effetti, le librerie delle collezioni utilizzano molto codice imperativo, ma ciò non significa che non dovremmo usarle. Come altri hanno già detto, usato con cura, qui ottieni davvero il meglio di entrambi i mondi.


Puoi spiegare perché pensi che Scala non sia un imperativo? list.filter (foo).sort (bar).take (10)- cosa potrebbe essere più imperativo? Grazie.
utente sconosciuto

Non vedo dove ha detto che Scala non è un imperativo.
Janx
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.