Как реализовать высокоскоростную последовательную выборку?

Типом приложения, которое нужно иметь в виду, является осциллограф или высокоскоростной регистратор данных. У меня есть функция, которая извлекает требуемую информацию, мне просто нужно решить, как ее повторять снова и снова, очень быстро и с высокой точностью.

Есть ограничения на time.sleep(), я не думаю, что это путь.

Я просмотрел встроенный планировщик событий, но я не думаю, что он достаточно точен и не совсем заполняет мои потребности.

Требования для этого:

  • Высокоскоростная выборка. 10 мс - это самое большее, о чем будет просить об этом.
  • Высокие интервалы точности. При 10 мс допустима ошибка 10% (± 1 мс).
  • Достаточно низкое потребление ЦП, некоторая загрузка приемлема в 10 мс, но она должна быть меньше ~ 5% за интервалы 100 мс и выше. Я знаю, что это субъективно, я полагаю, что я говорю о том, что hogging CPU неприемлем.
  • В идеале таймер будет инициализирован с интервалом времени, а затем запускается, когда это необходимо. Затем требуемую функцию следует вызывать с правильным интервалом снова и снова, пока таймер не остановится.
  • Он будет (не обязательно) работать только на машине Windows.

Существуют ли существующие библиотеки, которые отвечают этим требованиям? Я не хочу заново изобретать колесо, но если мне нужно, я, вероятно, буду использовать мультимедийный таймер Windows (winmm.dll). Любые комментарии/предложения с этим?

Ответ 1

Я знаю, что опаздываю на игру, отвечая на мой собственный вопрос, но, надеюсь, это поможет кому-то.

Я написал обертку для Windows Multimedia Timer только как тест. Кажется, что он работает хорошо, но код не полностью протестирован и не оптимизирован.

mmtimer.py:

from ctypes import *
from ctypes.wintypes import UINT
from ctypes.wintypes import DWORD

timeproc = WINFUNCTYPE(None, c_uint, c_uint, DWORD, DWORD, DWORD)
timeSetEvent = windll.winmm.timeSetEvent
timeKillEvent = windll.winmm.timeKillEvent


class mmtimer:
    def Tick(self):
        self.tickFunc()

        if not self.periodic:
            self.stop()

    def CallBack(self, uID, uMsg, dwUser, dw1, dw2):
        if self.running:
            self.Tick()

    def __init__(self, interval, tickFunc, stopFunc=None, resolution=0, periodic=True):
        self.interval = UINT(interval)
        self.resolution = UINT(resolution)
        self.tickFunc = tickFunc
        self.stopFunc = stopFunc
        self.periodic = periodic
        self.id = None
        self.running = False
        self.calbckfn = timeproc(self.CallBack)

    def start(self, instant=False):
        if not self.running:
            self.running = True
            if instant:
                self.Tick()

            self.id = timeSetEvent(self.interval, self.resolution,
                                   self.calbckfn, c_ulong(0),
                                   c_uint(self.periodic))

    def stop(self):
        if self.running:
            timeKillEvent(self.id)
            self.running = False

            if self.stopFunc:
                self.stopFunc()

Периодический тестовый код:

from mmtimer import mmtimer
import time

def tick():
    print("{0:.2f}".format(time.clock() * 1000))

t1 = mmtimer(10, tick)
time.clock()
t1.start(True)
time.sleep(0.1)
t1.stop()

Выход в миллисекундах:

0.00
10.40
20.15
29.91
39.68
50.43
60.19
69.96
79.72
90.46
100.23

Одноразовый тестовый код:

from mmtimer import mmtimer
import time

def tick():
    print("{0:.2f}".format(time.clock() * 1000))

t1 = mmtimer(150, tick, periodic=False)
time.clock()
t1.start()

Выход в миллисекундах:

150.17

Как вы можете видеть из результатов, это довольно точно. Тем не менее, это использование только time.clock(), поэтому возьмите их с щепоткой соли.

Во время длительного теста с периодическим таймером 10 мс, загрузка процессора составляет около 3% или меньше на моем старом двухцифровом 3GHz-устройстве. Машина также, похоже, использует это, когда он простаивает, поэтому я бы сказал, что использование дополнительного процессора минимально.

Ответ 2

Изменить: после написания нижеприведенного материала я был бы склонен реализовать аналогичный тест для планировщика событий python. Я не понимаю, почему вы считаете, что это будет недостаточно точно.

Что-то вроде следующего, похоже, работает очень хорошо под Linux со мной (и у меня нет причин думать, что он не будет работать с Windows). Вызывается каждые 10 мс, on_timer_event(), который печатает время со времени последнего вызова на основе часов реального времени. Это показывает приблизительную точность таймеров. Наконец, общее время распечатывается, чтобы показать, что нет дрейфа.

Кажется, что существует одна проблема с приведенным ниже кодом с событиями, иногда возникающими при ложных (и коротких интервалах). Я не знаю, почему это так, но, без сомнения, с некоторыми играми вы можете сделать его надежным. Я думаю, что такой подход - это путь.

import pygame
import time

pygame.init()
TIMER_EVENT = pygame.USEREVENT+1

pygame.time.set_timer(TIMER_EVENT, 10)

timer_count = 0
MAX_TIMER_COUNT = 1000

def on_timer_event():
    global last_time
    global timer_count

    new_time = time.time()

    print new_time - last_time
    last_time = new_time

    timer_count += 1

    if timer_count > MAX_TIMER_COUNT:
        print last_time - initial_time
        pygame.event.post(pygame.event.Event(pygame.QUIT, {}))

initial_time = time.time()
last_time = initial_time
while True:
    event = pygame.event.wait()
    if event.type == TIMER_EVENT:
        on_timer_event()

    elif event.type == pygame.QUIT:
        break