2014-10-14 16 views
14
user=> (def r (range 1)) 
user=> (for [a r, b r, c r, d r, e r, f r, g r, h r :when (and (= 0 a) (not= 1 b))] 
      (list a b c d e f g h)) 
((0 0 0 0 0 0 0 0)) 
user=> (doseq [a r, b r, c r, d r, e r, f r, g r, h r :when (and (= 0 a) (not= 1 b))] 
      (println (list a b c d e f g h))) 
CompilerException java.lang.RuntimeException: Method code too large!, compiling:(/tmp/form-init8346140986526777871.clj:1:1) 

Questo sembra venire da clojure.asm.MethodWriter. Il mio googling per questo errore con Clojure si presenta quasi senza colpi.Per vs. Doseq (e Codice metodo troppo grande)

Quindi ... che diavolo sta succedendo? Quanto è profonda questa tana del coniglio? Questa linea del codice Clojure produce davvero un metodo> 65 KB (il valore proviene dalla sorgente di MethodWriter)?

Se this answer colpisce il problema che sto incontrando, allora (a) perché il chunking significa che cresce in modo esponenziale anziché in modo lineare? E (b) quali sono le implicazioni per me come programmatore? Ad esempio, questo comportamento è ben noto e inteso? Dovrei evitare di usare doseq per qualsiasi situazione con più di 3 o 4 associazioni? In che modo si confronta con l'utilizzo di for e doall?

Forse correlati:

Clojure doseq generates huge code

Method code too large! exception using ASM

+2

Sembra che ogni ulteriore associazione nella tua doseq raddoppia la dimensione del codice generato (l'ho provato con no.disassemble). Non so perché. –

+0

+1 Wooha! Bella presa! Sono davvero interessato a saperne di più. – Chiron

+0

La mia ingenua aspettativa sarebbe quella tra 'for' e' doseq', sarebbe 'for' che produrrebbe il metodo più grande. Se 'doseq' ha questo tipo di inefficienza al di sotto, allora sarei interessato a sapere a quale soglia della complessità si ottengono prestazioni migliori con (' ign ignora (doall (per [materiale] (effetto collaterale))) "invece del più naturale" (doseq [effetto collaterale]) ". – galdre

risposta

7

Quello che stai vedendo è un effetto collaterale sgradevole di un'ottimizzazione che è stato messo nella realizzazione del doseq macro per gestire chunked sequences in l'input. La risposta della domanda che hai collegato descrive correttamente la causa sottostante, ma non getta molta luce sul perché le cose accadono nel modo in cui lo fanno.

Il doseq attuazione utilizza internamente una funzione che costruisce ricorsivamente una serie di nidificate loop costrutti, uno loop per ciascun livello di binding in doseq. In una versione ingenua e non ottimizzata di questa implementazione, il ciclo ad ogni livello eseguirà semplicemente il proprio corpo e quindi chiamerà recur con il valore next per il suo seq. Qualcosa in questo senso:

(loop [s (seq input)] 
    (if s 
    (do (run-body (first s)) 
     (recur (next s))))) 

Se questo ss sembra essere una sequenza Chunked, però, questo farà sì che la creazione non necessaria di un sacco di oggetti ss intermedi che non vengono mai utilizzate al di fuori del corpo del ciclo. L'ottimizzazione che doseq ha apportato consiste nel mettere uno if all'interno dello loop con un ramo per gestire le sequenze in blocchi e uno per gestire le sequenze non suddivise in blocchi. Il corpo del loop è duplicato tra ciascun ramo. Se il corpo del ciclo si presenta come un ciclo nidificato, puoi vedere come avviene l'aumento esponenziale delle dimensioni del codice: il ciclo ad ogni livello del codice espanso ha due anelli figlio.

Quindi, per rispondere alla tua domanda, non direi esattamente che l'esplosione nella dimensione del codice è intesa, ma è una conseguenza del comportamento progettato di doseq. Semplicemente non è stato progettato per gestire loop profondamente annidati, e in natura non l'ho mai visto usato con più di uno o due livelli di binding.

è possibile riprodurre la semantica di una nidificazione doseq con una combinazione di for e dorun (non utilizzato doall come questo mantiene inutilmente la testa della ss). Ciò ti consentirà di gestire qualsiasi livello di nidificazione, con un risultato di prestazioni lieve ma misurabile se ti capita di correre attraverso una sequenza biforcuta in un ciclo stretto.

user> (time (doseq [x (range 10000) y (range 10000)] (* x y))) 
"Elapsed time: 2933.543178 msecs" 

user> (time (dorun (for [x (range 10000) y (range 10000)] (* x y)))) 
"Elapsed time: 5560.90003 msecs" 
1

Ho avuto un problema simile quando stavo creando il mio compilatore utilizzando Java.

Ho dichiarato una matrice molto grande. Per me la soluzione è stata divisa in una piccola matrice. E 'solo un suggerimento, forse si può fare qualcosa di simile, come ad esempio:

(def r (range 1)) 
(defn foo [a b c d] 
    (doseq [e r, f r, g r, h r] (println "Hi"))) 
(doseq [a r, b r, c r, d r :when (and (= 0 a) (not= 1 b))] 
    (foo a b c d)) 
+2

Penso che la tua risposta potrebbe essere buona ma sarebbe utile fornire maggiori dettagli – StormeHawke

+0

Questo in realtà non risponde alla domanda. Se hai una domanda diversa, puoi richiederla facendo clic su [Invia domanda] (http://stackoverflow.com/questions/ask). Puoi anche [aggiungere una taglia] (http://stackoverflow.com/help/privileges/set-bounties) per attirare maggiormente l'attenzione su questa domanda una volta che hai abbastanza [reputazione] (http://stackoverflow.com/help/ che cosa è-la reputazione). – vyegorov

+4

@vyegorov, in realtà la sua risposta fornisce tutte le informazioni necessarie per trovare una soluzione alternativa. Non sto cercando esattamente una soluzione, ma la sua risposta è stata sicuramente più utile delle tue recensioni incise sulle scorte. – galdre