2015-07-18 18 views
5

Come implementare l'elaborazione di un array con precisione millisecondo utilizzando python in linux (in esecuzione su un singolo core Raspberry Pi).Elaborazione del sub millisecondo in python senza busywait

Sto tentando di analizzare le informazioni da un file MIDI, che è stato pre-elaborato su un array in cui ogni millisecondo controllo se l'array ha voci al timestamp corrente e attiva alcune funzioni se lo fa.

Attualmente sto utilizzando time.time() e impieghiamo l'attesa di occupato (come concluso here). Questo mangia tutta la CPU, quindi opto per una soluzione migliore.

# iterate through all milliseconds 
for current_ms in xrange(0, last+1): 
    start = time() 

    # check if events are to be processed 
    try: 
    events = allEvents[current_ms] 
    # iterate over all events for this millisecond 
    for event in events: 
     # check if event contains note information 
     if 'note' in event: 
     # check if mapping to pin exists 
     if event['note'] in mapping: 
      pin = mapping[event['note']] 
      # check if event contains on/off information 
      if 'mode' in event: 
      if event['mode'] == 0: 
       pin_off(pin) 
      elif event['mode'] == 1: 
       pin_on(pin) 
      else: 
       debug("unknown mode in event:"+event) 
      else: 
      debug("no mapping for note:" + event['note']) 
    except: 
    pass 

    end = time() 

    # fill the rest of the millisecond 
    while (end-start) < (1.0/(1000.0)): 
    end = time() 

dove last è il millisecondo dell'ultimo evento (noto dalla pre-elaborazione)

Questa non è una domanda circa time() vs clock() più su sleep vs busy wait.

Non riesco a dormire nel ciclo di "riempimento resto di millisecondo", a causa dello too lowaccuracy of sleep(). Se dovessi usare ctypes, come faccio a farlo correttamente?

C'è qualche libreria Timer che richiama in modo affidabile un callback ogni millisecondo?

La mia attuale implementazione è GitHub. Con questo approccio ottengo un disallineamento di circa 4 o 5ms sul drum_sample, che è 3.7s totale (con finto, quindi nessun hardware reale collegato). Su un campione 30.7s, l'inclinazione è di circa 32 ms (quindi almeno non lineare!).

Ho provato con time.sleep() e nanosleep() via ctypes con il seguente codice

import time 
import timeit 
import ctypes 
libc = ctypes.CDLL('libc.so.6') 

class Timespec(ctypes.Structure): 
    """ timespec struct for nanosleep, see: 
     http://linux.die.net/man/2/nanosleep """ 
    _fields_ = [('tv_sec', ctypes.c_long), 
       ('tv_nsec', ctypes.c_long)] 

libc.nanosleep.argtypes = [ctypes.POINTER(Timespec), 
          ctypes.POINTER(Timespec)] 
nanosleep_req = Timespec() 
nanosleep_rem = Timespec() 

def nsleep(us): 
    #print('nsleep: {0:.9f}'.format(us)) 
    """ Delay microseconds with libc nanosleep() using ctypes. """ 
    if (us >= 1000000): 
    sec = us/1000000 
    us %= 1000000 
    else: sec = 0 
    nanosleep_req.tv_sec = sec 
    nanosleep_req.tv_nsec = int(us * 1000) 

    libc.nanosleep(nanosleep_req, nanosleep_rem) 

LOOPS = 10000 

def do_sleep(min_sleep): 
    #print('try: {0:.9f}'.format(min_sleep)) 
    total = 0.0 
    for i in xrange(0, LOOPS): 
    start = timeit.default_timer() 
    nsleep(min_sleep*1000*1000) 
    #time.sleep(min_sleep) 
    end = timeit.default_timer() 
    total += end - start 
    return (total/LOOPS) 

iterations = 5 
iteration = 1 
min_sleep = 0.001 
result = None 
while True: 
    result = do_sleep(min_sleep) 
    #print('res: {0:.9f}'.format(result)) 
    if result > 1.5 * min_sleep: 
     if iteration > iterations: 
     break 
     else: 
     min_sleep = result 
     iteration += 1 
    else: 
     min_sleep /= 2.0 

print('FIN: {0:.9f}'.format(result)) 

Il risultato sulla mia i5 è

FIN: 0,000165443

mentre sul RPI è

FIN: 0,000578617

che suggeriscono un periodo di sonno di circa 0,1 o 0,5 millisecondi, con il dato jitter (tendono a dormire più a lungo), che al massimo mi aiuta a ridurre il carico un po '.

+0

Hai provato a utilizzare 'sleep()' o a testarne le prestazioni sul tuo sistema? [La seconda risposta] (http://stackoverflow.com/a/15967564/3004881) dopo quella collegata indica che potrebbe avere una precisione più che sufficiente per te. –

+0

sì ho fatto (vedi domanda aggiornata), e il link suggerisce tempi di sonno oltre 1ms, sto cercando soluzioni di seguito (dal momento che 1ms sarà molto probabilmente superato). Non riesci a verificare l'accuratezza di 1μs, quindi forse ancora dormendo bene e la misurazione è appena fuori? – x29a

+0

È necessario sapere che se si utilizza un'interfaccia MIDI convenzionale, è necessario 1 ms per trasmettere un messaggio "nota su" singolo (3 ottetti). Potresti cercare una precisione che lo strumento non può soddisfare. – msw

risposta

3

Una possibile soluzione, utilizzando il modulo sched:

import sched 
import time 

def f(t0): 
    print 'Time elapsed since t0:', time.time() - t0 
s = sched.scheduler(time.time, time.sleep) 

for i in range(10): 
    s.enterabs(t0 + 10 + i, 0, f, (t0,)) 
s.run() 

Risultato:

Time elapsed since t0: 10.0058200359 
Time elapsed since t0: 11.0022959709 
Time elapsed since t0: 12.0017120838 
Time elapsed since t0: 13.0022599697 
Time elapsed since t0: 14.0022521019 
Time elapsed since t0: 15.0015859604 
Time elapsed since t0: 16.0023040771 
Time elapsed since t0: 17.0023028851 
Time elapsed since t0: 18.0023078918 
Time elapsed since t0: 19.002286911 

A parte qualche compensazione di circa 2 millisecondi (che si può calibrare) costante, il jitter sembra essere nell'ordine di 1 o 2 millisecondi (come riportato dallo stesso time.time). Non sono sicuro che sia abbastanza buono per la tua applicazione.

Se è necessario eseguire qualche lavoro utile nel frattempo, è necessario esaminare il multi-threading o il multi-processing.

Nota: una distribuzione Linux standard eseguita su un RPi non è un sistema operativo in tempo reale. Anche Python può mostrare tempi non deterministici, ad es. quando inizia una garbage collection. Quindi il tuo codice potrebbe funzionare bene con jitter basso per la maggior parte del tempo, ma potresti avere occasionali "intoppi", dove c'è un po 'di ritardo.

+0

Grazie, farò un tentativo. Poiché alcune delle song MIDI sono 20 minuti, ciò risulterebbe in voci di scheduler di 1.2M, vediamo se può gestirle. Per quanto riguarda il "meantime", no, tutto ciò che dovrebbe fare è attivare le funzioni al momento giusto. Il resto sono cose del sistema operativo. – x29a

+0

Non dovrebbe essere un grosso problema, immagino. Se si guarda il [codice sorgente] (https://hg.python.org/cpython/file/2.7/Lib/sched.py), è piuttosto semplice. Utilizza un [heap] (https://docs.python.org/2/library/heapq.html) per archiviare tutti gli eventi in un elenco e l'efficienza ottiene il successivo, un elenco di Python di 1 milione di elementi non è normalmente un problema se hai la memoria. Il timing è basato su 'timefunc' e' delayfunc' si passa a 'scheduler'. Nel mio esempio, utilizzo 'time.time' e' time.sleep'. Non ho esperienza con un RPi, ma potresti essere in grado di sostituire quelli con alcuni timer hardware. –