2015-05-22 27 views
13

Mi sono imbattuto di recente nella parola chiave yield in Python (così come in JavaScript) - Capisco che questo sia usato primariamente per il modello di generatore, ma il costrutto del linguaggio sembra essere usato anche nelle funzioni asincrone dove si trovano i miei interessi. Nelle funzioni asincrone può semplicemente agire come zucchero sintatico e so che esistono schemi alternativi per ottenere lo stesso effetto - Ma mi piace - MOLTO!È possibile implementare la funzionalità di rendimento di Python nella C libera?

Voglio sapere se riesco a fare qualcosa di simile in C (anche con il montaggio in linea). Mi sono imbattuto in un'implementazione Java usando i thread https://github.com/mherrmann/java-generator-functions che posso più o meno implementare in C. Tuttavia questa non sarà un'implementazione indipendente, e il mio interesse è puramente in un'implementazione indipendente.

Venendo a C co-routines (http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html), uno dei deficit è che gli oggetti dello stack non possono essere utilizzati. Tuttavia, sto ancora bene con questo poiché le attuali implementazioni di callback asincrone non possono nemmeno usare lo stack. Tuttavia, il problema si trova in un'implementazione indipendente - Non riesco a pensare a un modo per raccogliere tutte le variabili del registro e memorizzarle senza un ambiente ospitato.

Probabilmente c'è una soluzione che utilizza lo setjmp/longjmp, tuttavia sono abbastanza sicuro che questi non possano essere implementati come indipendenti.

Quindi la domanda è: è possibile implementare la funzionalità di rendimento Python in Libera installazione C?

Personalmente penso di aver esaurito le possibilità, quindi ti chiederò questo: se potessi avere un'implementazione ospitata, come la implementeresti (preferibilmente con qualche magia magica)? Ho un'implementazione abbastanza brutta che posterò più avanti se non verrà fuori nulla di interessante.

Anche io non voglio implementazioni in C++ - A meno che non si possa avvolgere il C++ con pure funzioni C.

MODIFICA: Un requisito di base è che la funzione del generatore deve essere reinserente.

+0

Dato che l'interprete Python è scritto in C ... sì? – chepner

+1

Non penso. O hai bisogno della libreria standard (che implementa 'setjmp' e' longjmp'), o devi fare alcune operazioni specifiche dell'hardware, molto probabilmente coinvolgendo il montaggio. Quanto indipendente lo vuoi? – mtijanic

+0

@mtijanic Le operazioni specifiche dell'hardware vanno bene. Quello che voglio è - Se potessi modificare un compilatore C e introdurre una sintassi di yield allora quella sintassi dovrebbe essere disponibile nella prima fase della compilazione di un compilatore (quando può compilare solo il codice indipendente). Non dovrebbe dipendere da funzioni specifiche del sistema operativo come quando si costruisce la seconda fase del compilatore. Non sono sicuro se quello che ho appena detto abbia senso per chiunque. – tinkerbeast

risposta

3

Ho intenzione di rispondere utilizzando setjmp e longjmp poiché tali interfacce sono standard e si può facilmente trovare le loro implementazioni per qualsiasi piattaforma HW. Sono indipendenti, ma dipendenti dall'HW.

struct _yield_state { 
    jmp_buf buf; 
    _Bool yielded; 
}; 

#define yieldable static struct _yield_state _state; \ 
        if (_state.yielded) longjmp(_state.buf, 1); else {} 


#define yield(x) if (setjmp(_state.buf)) { _state.yielded = false;   }\ 
        else     { _state.yielded = true; return x } 

int func(int a, int b) 
{ 
    yieldable; 

    if (a > b) 
     yield(0); 

    return a + b; 
} 

È possibile trovare un esempio setjmp e longjmp implementazione here. È puro assemblaggio, specifico solo per l'hardware sottostante.

+0

Poiché questa soluzione utilizza oggetti statici, la funzione è non-rentrante. Ciò porterà a molti problemi. Comunque penso che tu abbia ragione riguardo a setjmp e longjmp essendo indipendente - Sia LLVM che GCC sembrano avere builtin per questi http://llvm.org/docs/ExceptionHandling.html#sjlj-intrinsics – tinkerbeast

+0

@tinkerbeast True. È possibile modificarlo per accettare un argomento di stato dal chiamante, possibilmente con qualcosa come '#finefine func (a, b) func (a, b, & state)', ma poi l'onere è sul chiamante per allocare lo stato. Un'altra opzione è che l'oggetto statico sia un elenco di stati e ne scegli uno basato sull'argomento passato dal chiamante. Tale argomento potrebbe essere un threadId, '__FUNC__ 'del chiamante o anche un numero casuale generato al compiletime. – mtijanic

+0

non credi che questo piccolo pezzo di codice abbia troppi modi per rompere? – HuStmpHrrr

5

Iterator in Python seguono questo schema: li chiami (con argomenti) e restituiscono un oggetto. Si chiama ripetutamente il metodo .next() o il metodo .__next__() dell'oggetto e viene eseguito attraverso l'iteratore.

Possiamo fare qualcosa di simile:

typedef struct iterator{ 
    int yield_position; /* Where to jump to */ 
    void *yield_state; /* opaque container for local variables */ 
    void *(*next)(iterator*); /* Function taking "this" argument 
           returning a pointer to whatever we yielded */ 
} iterator; 

iterator *make_generator(/* arguments? */){ 
    iterator *result = malloc(sizeof(iterator)); /* Caller frees */ 
    result->yield_position = 0; 
    /* Optionally allocate/initialize yield_state here */ 
    result->next = do_generator; 
    return result; 
} 

void *do_generator(iterator *this){ 
    struct whatever *result; 
    switch(this->yield_position){ 
     case 0: 
      /* Do something */ 
      this->yield_position = 1; 
      /* Save local variables to this->yield_state if necessary */ 
      return (void *) result; 
     case 1: 
      /* Initialize local variables from this->yield_state */ 
      /* Etc.*/ 
    } 
} 

void free_generator(iterator *iter){ 
    /* Free iter->yield_state if necessary */ 
    free(iter); 
} 

Dal caso etichette can be used just about everywhere, l'interruttore deve essere in grado di esempio saltare nel mezzo di un ciclo se necessario. Probabilmente avrai ancora bisogno di reinizializzare le variabili del ciclo, ecc.

Si chiama così:

iterator *iter = make_generator(/* arguments? */); 
struct whatever *foo = iter->next(iter); 
/* etc. */ 
free_generator(iter); 

Passando all'argomento this a mano viene noioso, in modo da definire una macro:

#DEFINE NEXT(iter) ((iter)->next(iter)) 
+0

se si libera 'iter' nell'esempio si desidera liberare' foo' pure. inoltre questo non copre le dichiarazioni di rendimento multiplo in una funzione giusta (il vero potere delle coroutine)? – Alex

+0

@Alex: Si * potrebbe * voler liberare 'pippo'. È possibile che tu stia internando i tuoi valori di ritorno o facendo qualche altra cosa intelligente come il conteggio. Ho deliberatamente lasciato il vago su questo punto per ospitare un'ampia varietà di casi d'uso. E sì, ottieni più dichiarazioni di rendimento. Basta aggiungere più etichette di casi. Nota che ogni volta che vuoi cedere devi fare l'intera danza salva-e-ristabilisci i locali. – Kevin

+0

Quello che * non * ottiene è 'yield from'. Questo è ancora un po 'complicato, specialmente se si desidera aggiungere il supporto per '.send()' ecc. – Kevin

4

Ignorando il gergo specifiche della lingua, quello che stai cercando si chiama "coroutine". Simon Tatham ha inventato qualcosa che sembra e si comporta in modo molto simile alle coroutine con qualche magia per il preprocessore. Non è interamente funziona allo stesso modo, ma si finge in un modo che è utile per la maggior parte dei casi.

Vedere here per tutti i dettagli.

A seconda del tuo problema, questo potrebbe essere sufficiente o potrebbe non esserlo. Il vantaggio di questo metodo, in ogni caso, è che funziona con lo standard C; nessuna necessità di compilatori non standard.