более эффективная линейная интерполяция?

Я пишу механизм гранулярного синтеза с линейной интерполяцией. Проблема в том, что интерполяция в том виде, в каком она реализована в настоящее время, примерно на 10% тяжелее для процессора, чем обработка без интерполяции.

Я публикую упрощенный пример программы, демонстрирующий обработку, которую я применяю к чередующемуся буферу сэмплов. Есть ли способ сделать вычисления внутри функции interpolation более эффективными и, следовательно, менее тяжелыми для процессора? Функция работает отлично и обеспечивает очень плавное звучание звука. Это просто потребление процессора, которое должно быть оптимизировано.

Я пишу код в свободное время, поэтому я не очень опытный программист. Кажется, я не могу уложить это в голове. Боролся с этим уже несколько недель.

Я отредактировал код, чтобы исправить несоответствие типа переменной dist. Еще одно редактирование, чтобы избежать усеченных результатов int/long.

int main(void) {
    long i;
    long oldlength = 5000;
    long newlength = 10000;
    double dist;
    double oldbuffer[oldlength]; // assume the buffer contains values
    double newbuffer[newlength];

    // method with linear interpolation
    for (i = 0; i < newlength; i++) {
        dist = ((double)i / (double)newlength) * (double)oldlength;
        newbuffer[i] = interpolation(dist, oldbuffer, 1, 0);
    }

    // method without linear interpolation
    for (i = 0; i < newlength; i++) {
        dist = ((double)i / (double)newlength) * (double)oldlength;
        newbuffer[i] = oldbuffer[(long)dist];
    }

    return 0;
}

double interpolation(double dist, float *buffer, short chcount, short ch) {
    //  dist    =   current read position in buffer
    //  buffer  =   interleaved sample buffer
    //  chcount =   channels contained in sample buffer
    //  ch      =   channel to be read from buffer

    long i = (long)dist; // get truncated index
    dist -= (long)dist; // calculate fraction value for interpolation

    return buffer[i * chcount + ch] + dist * (buffer[(i + 1) * chcount + ch] - buffer[i * chcount + ch]);
}

person Matthias Müller    schedule 22.01.2015    source источник
comment
если i < newlength (условие в циклах for) истинно, результатом (i / newlength) будет 0.   -  person mch    schedule 22.01.2015
comment
конечно, dist в первую очередь должно быть double. Я отредактировал код.   -  person Matthias Müller    schedule 22.01.2015
comment
buffer объявлен как float*, но на самом деле вы используете double*.   -  person Persixty    schedule 22.01.2015
comment
ваше редактирование не изменило проблему, которую я упомянул.   -  person mch    schedule 22.01.2015
comment
Хорошо, я думаю, что это решает проблему сейчас. Немного грубо, но теперь должно быть правильно, верно? ;-)   -  person Matthias Müller    schedule 22.01.2015
comment
Я думаю, вам лучше использовать только числа double вместо постоянного преобразования между long и double и обратно.   -  person Luis Colorado    schedule 23.01.2015


Ответы (1)


В вашем текущем коде есть одна заметная проблема. Две строки:

dist = (i / newlength) * oldlength;

всегда будет возвращать 0, потому что i и newlength являются целыми числами, поэтому i / newlength вернет целочисленный результат. Чтобы исправить это, просто выполните:

dist = ((double) i / newlength) * oldlength;

Далее, как вы измеряете, что код интерполяции требует на 10% больше процессорного времени? Код как есть выполняется так быстро, что любое измерение времени/профиля было бы в основном бессмысленным. Это также сочетается с тем фактом, что любой приличный компилятор, вероятно, мог бы полностью оптимизировать циклы в сборке релиза.

Я сделал два быстрых изменения в вашем коде, чтобы сделать результаты бенчмаркинга более значимыми. Один из них - повторить интерполяционные тесты несколько раз, например:

long NUMTESTS = 100000;
...
for (int j = 0; j < NUMTESTS; ++j) {
    for (i = 0; i < newlength; i++) {
        dist = ((double) i / newlength) * oldlength;
        newbuffer[i] = interpolation(dist, oldbuffer, 1, 0);
    }
}

При необходимости отрегулируйте значение NUMTESTS, чтобы дать вам по крайней мере несколько секунд времени выполнения для каждого теста. Во-вторых, убедиться, что компилятор не может оптимизировать циклы. В конце сделайте что-то вроде:

double sum = 0;
...
for (i = 0; i < newlength; ++i)
{
    sum += newbuffer[i];
}

printf("buffer = %f\n", sum);

С этими изменениями в VS2013 я получаю одинаковое время выполнения 6,6 секунды как для циклов с интерполяцией, так и для циклов без интерполяции (используя 100 000 тестовых циклов, как указано выше).

person uesp    schedule 22.01.2015
comment
Вау, спасибо @uesp за этот обширный тест! В моем фактическом коде все вычисления выполняются с правильными типами (double), поэтому никаких потерь из-за целых чисел. Я предполагаю, что мой простой пример был немного ошибочным. На самом деле я пишу внешний Max/MSP в реальном времени с API Max/MSP. Конечно, Макс позволяет вам контролировать использование процессора, отсюда и мои 10%. - person Matthias Müller; 22.01.2015