Комментарии 42
Который раз уже вижу подобные сравнения (:
Вообще, про это в офф. документации написано хорошо:
Так что, не во всех задачах C++ даст такой большой прирост производительности.
Но в статье меня кое-что другое интересует — каким образом это тестировалось? Какие алгоритмы? На каких контейнерах? И т.д.
Потому что, если говорить про Java, быть может у вас большую часть времени работы занимали не сами алгоритмы, а работа с контейнерами как таковая.
Вообще, про это в офф. документации написано хорошо:
Typical good candidates for the NDK are self-contained, CPU-intensive operations that don't allocate much memory, such as signal processing, physics simulation, and so on.
Так что, не во всех задачах C++ даст такой большой прирост производительности.
Но в статье меня кое-что другое интересует — каким образом это тестировалось? Какие алгоритмы? На каких контейнерах? И т.д.
Потому что, если говорить про Java, быть может у вас большую часть времени работы занимали не сами алгоритмы, а работа с контейнерами как таковая.
+1
Именно сам алгоритм. Вот как то так:
public static boolean ColorDodgeGray(Mat mat, Mat top){
Log.i(TAG,"ColorDodgeGray BEGIN");
NativeUtils.nativeColorDodgeGray(mat.getNativeObjAddr(), top.getNativeObjAddr());
/*for (int i=0; i<mat.rows(); i++){
for (int j=0; j<mat.cols(); j++) {
double[] matPix = mat.get(i, j);
double[] topPix = top.get(i, j);
if (matPix[0]+topPix[0]>255){
matPix[0] = 255.;
} else {
matPix[0] = (255. * matPix[0]) / (256. - topPix[0]);
}
mat.put(i, j, matPix);
}
}*/
Log.i(TAG,"ColorDodgeGray END");
return true;
}
+1
Я так понял что аналог нейтивному коду — это закоментированный код?
Если так, тогда это просто неправильный подход. В Android есть быстрые средства для работы с изображениями. Смотрите например ColorMatrix и Paint.setXfermode.
Если же нужно обработать именно Mat, тогда очевидно лучшим решением будет использовать предоставляемые методы, или сконвертировать его в Bitmap и использовать упомянутые методы.
Если так, тогда это просто неправильный подход. В Android есть быстрые средства для работы с изображениями. Смотрите например ColorMatrix и Paint.setXfermode.
Если же нужно обработать именно Mat, тогда очевидно лучшим решением будет использовать предоставляемые методы, или сконвертировать его в Bitmap и использовать упомянутые методы.
+5
Ну и раз речь идет о попиксельной обработке то нужно упомянуть о open gl шейдерах. Этот подход «порвет как тузик тряпку» NDK код.
+3
Я так понял что аналог нейтивному коду — это закоментированный код?
Совершенно верно.
Если же нужно обработать именно Mat, тогда очевидно лучшим решением будет использовать предоставляемые методы, или сконвертировать его в Bitmap и использовать упомянутые методы
А что Вы подразумеваете под предоставляемыми методами? Методы OpenCV? Так я их и использую в 90% случаев и только там, где нет подходящего метода, пишу свой. Был, кстати, еще случай, когда приложение вылетало при применении стандартной функции OpenCV — поэлементного умножения матриц. Вот тут тоже пришлось переписать ее на C++.
Что касается преобразований Mat -> Bitmap и обратно, то это очень затратные функции и по времени и по памяти.
0
Ну как же, вот у вас работа с коллекциями: в каждой итерации цикла 2 раза Mat.get и 1 Mat.put. Кто его знает, сколько они времени тянут. В С++ версии они тоже есть?
0
Там их заменяют
uchar matPix = mat.at<uchar>(i, j);
mat.at<uchar>(i, j) = matPix;
0
Но в нативном коде это шаблонный класс, а значит, наверняка инлайновый и прекрасно оптимизируется (и из его метода возвращается ровно один байт!), а вот
В общем, Java-код действительно странноват.
Вот, например, существенно более быстрый способ: ровно одно копирование.
Ну и, rows() и cols() стоило бы вызвать один раз.
double[] matPix = mat.get(i, j);
судя по возвращаемому массиву по-любому делает копирование массива (rows*cols раз!), из которого потом используется ровно один элемент…В общем, Java-код действительно странноват.
Вот, например, существенно более быстрый способ: ровно одно копирование.
Ну и, rows() и cols() стоило бы вызвать один раз.
+2
Вот, например, существенно более быстрый способ: ровно одно копирование.
Верно. Только это удвоение (утроение в данном примере) используемой памяти. А если все операции происходят с 13 MP картинкой?
0
Даже так, можно делать то же самое построчно, один раз создав буфер. Уж на четыре килобайта памяти точно хватит.
+1
К тому же, этот буфер можно сделать типа byte[], что более эквивалентно NDK-коду.
0
Я согласен. В некритичных случаях приведенный Вами пример вполне подходит.
0
Я всё-таки говорю о том, что даже «критичный» случай на Java может работать существенно быстрее с минимумом усилий.
В данном случае, в нативном коде для get() вместо аллокации массива через NewDoubleArray на каждом пикселе делалось бы GetPrimitiveArrayCritical (одна аллокация на весь массив).
Отсюда вопрос: во сколько раз это было бы быстрее?
В данном случае, в нативном коде для get() вместо аллокации массива через NewDoubleArray на каждом пикселе делалось бы GetPrimitiveArrayCritical (одна аллокация на весь массив).
Отсюда вопрос: во сколько раз это было бы быстрее?
+2
В выходные постараюсь проверить. Самому интересно.
+2
Буду ждать, мне тоже интересно. :)
Опять же, про double всё ещё не очень понятно: поскольку в «NDK-коде» используется uchar, то я полагаю, что матрица используется CV_8U? Если так, то даже сами по себе методы get и put делают поэлементное преобразование типов вместо выполнения обычного memcpy, что также не даёт преимуществ Java-коду. Проверьте заодно и это, если не затруднит.
Опять же, про double всё ещё не очень понятно: поскольку в «NDK-коде» используется uchar, то я полагаю, что матрица используется CV_8U? Если так, то даже сами по себе методы get и put делают поэлементное преобразование типов вместо выполнения обычного memcpy, что также не даёт преимуществ Java-коду. Проверьте заодно и это, если не затруднит.
+2
Тоже отмечусь, чтобы посмотреть чем закончилось.
0
Попробую дать развернутый ответ.
Во-первых, вот этот код не работает.
В приведенном мною выше примере код
работает для матрицы любого типа. Если же мы создадим массив типа double и попробуем туда скопировать данные матрицы типа CV_8U
то произойдет исключение. Залез в исходники OpenCV — все верно, проверяется, правильного ли типа матрица, если нет — возбуждается исключение. Попробовал объявить нужный тип буфера — как сказано здесь. Переписал вышеприведенный код на этот:
Все работает, но вместо эффекта Pencil sketch у меня получается кадр из фильма «Хищник». Почему так? — byte[] в Java — массив со знаком. В OpenCV get и put с массивами типа uchar (пока?) не работают. Но этот оптимизированный код работает быстро. У меня получилось 60, 50 и 50 миллисекунд против 30, 20 и 20 неоптимизированного NDK. Создание еще одного (двух в моем случае) буферных массивов типа char, преобразование данных, работа с ними, затем обратное преобразование возможно даст эффект, но, по-моему, использование NDK для этих целей проще.
Во-первых, вот этот код не работает.
В приведенном мною выше примере код
double[] matPix = mat.get(i, j);
работает для матрицы любого типа. Если же мы создадим массив типа double и попробуем туда скопировать данные матрицы типа CV_8U
double[] buff = new double[size];
mat.get(0, 0, buff);
то произойдет исключение. Залез в исходники OpenCV — все верно, проверяется, правильного ли типа матрица, если нет — возбуждается исключение. Попробовал объявить нужный тип буфера — как сказано здесь. Переписал вышеприведенный код на этот:
int size = mat.cols();
for (int i=0; i<mat.rows(); i++){
byte[] matPix = new byte[size];
byte[] topPix = new byte[size];
mat.get(i, 0, matPix);
top.get(i, 0, topPix);
for (int j=0; j<size; j++) {
if (matPix[j]+topPix[j]>255){
matPix[j] = (byte) 255;
} else {
matPix[j] = (byte) ((255 * matPix[j]) / (256 - topPix[j]));
}
}
mat.put(i, 0, matPix);
}
Все работает, но вместо эффекта Pencil sketch у меня получается кадр из фильма «Хищник». Почему так? — byte[] в Java — массив со знаком. В OpenCV get и put с массивами типа uchar (пока?) не работают. Но этот оптимизированный код работает быстро. У меня получилось 60, 50 и 50 миллисекунд против 30, 20 и 20 неоптимизированного NDK. Создание еще одного (двух в моем случае) буферных массивов типа char, преобразование данных, работа с ними, затем обратное преобразование возможно даст эффект, но, по-моему, использование NDK для этих целей проще.
+1
Про то, что буфер должен быть byte, я уже два раза написал. :)
Попробуйте вот так:
(ну и, понятно, дальше работать с этими целыми, а не с массивами, заодно проверка на ">255" будет корректной). Вроде, должно сработать правильно.
И, кстати, спасибо за тест. :)
Попробуйте вот так:
int mp = (matPix[j] & 0xFF);
int tp = (topPix[j] & 0xFF);
(ну и, понятно, дальше работать с этими целыми, а не с массивами, заодно проверка на ">255" будет корректной). Вроде, должно сработать правильно.
И, кстати, спасибо за тест. :)
0
А, и ещё забыл: надо было «new byte[size]» написать до цикла, чтобы аллоцировать память один раз, а не rows() раз. Может оказаться ещё немного быстрее.
0
Не очень понял смысл конъюнкции в данном случае.
Если matPix[j] выражение имеет тип byte (или char в С), и если конъюнкция использовалась, чтобы предостеречься от записи мусора, то и Java и C определяют так называемый integral promotion, который гарантированно избавит от побитовых нелепостей.
Если matPix[j] выражение имеет тип byte (или char в С), и если конъюнкция использовалась, чтобы предостеречься от записи мусора, то и Java и C определяют так называемый integral promotion, который гарантированно избавит от побитовых нелепостей.
0
И, кстати, спасибо за тест. :)
Вам спасибо за ценные советы. Дополнил статью новыми тестами.
0
Если хотите перформанса, е используйте конструкции вида mat.at(idx, idy), при многократном обращении таким образом(например в цикле по всей картинке) перформанс ощутимо проседает. Лучше ковырнуть сырой указатель примерно так: (uchar)mat.data и работать с ним… так получается быстрее для поэлемнтной обработки. Да и имеет смысл в таких местах задуматься об использовании NEON или чегото подобного.
0
Neon использовали?
+1
Природу не обманешь:) Процессор умеет лишь выполнять инструкции, и ему пофиг что это за инструкции — Вашей программы, JIT-компилятора или интерпретатора байт-кода. Хотя прошлый раз народ почему-то не согласился :)
-8
Renderscript пробовали?
0
Я вот только не понял — почему на древнем nexus one работает все в пару раз быстрее, чем на Nexus 7, который уже, конечно, не самый новый девайс, но 4 ядра и гиг оперативки все же имеет
+1
НЛО прилетело и опубликовало эту надпись здесь
Ну молодцы, конечно… Из JAVA считать мульоны пикселей и жаловаться, что все плохо. Опускаем на уровень NDK и пишем какой код для трансформаций (да, я писал такой) – полуаем терпимый результат. Но не более. Пишем то же самое на шейдерах (да, прямо в java классе) – и радуемся отличной производительности, + никакой возни с NDK.
+1
и на засыпку… Мне лень не нельзя было ставить OpenCV, и мне пришлось разбирался с цветовыми моделями. У вас точное совпадение по цветам получилось с фотошопом, используя openCV? Результаты тестирования алгоритмов различных реализаций дали разные результаты: тот же gimp и photoshop вели себя по раному. Фотошопный вариант у меня получился только с применением шейдеров.
0
Что за класс Mat?
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Публикации
Изменить настройки темы
Android SDK vs NDK — сравнение производительности однотипных участков кода