【问题标题】:Fast logarithm calculation快速对数计算
【发布时间】:2016-02-21 08:41:06
【问题描述】:

所有代码都在 linux 上的同一台机器上运行。

在python中:

import numpy as np
drr = abs(np.random.randn(100000,50))
%timeit np.log2(drr)

10 个循环,3 个循环中的最佳:每个循环 77.9 毫秒

在 C++ 中(使用 g++ -o log ./log.cpp -std=c++11 -O3 编译):

#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <random>
#include <ctime>
int main()
{
std::mt19937 e2(0);
std::normal_distribution<> dist(0, 1);
const int n_seq = 100000;
const int l_seq = 50;
static double x[n_seq][l_seq];
for (int n = 0;n < n_seq; ++n) {
  for (int k = 0; k < l_seq; ++k) {
    x[n][k] = abs(dist(e2));
    if(x[n][k] <= 0)
      x[n][k] = 0.1;
    }
  }
 clock_t begin = clock();

 for (int n = 0; n < n_seq; ++n) {
   for (int k = 0; k < l_seq; ++k) {
     x[n][k] = std::log2(x[n][k]);
       }
  }
  clock_t end = clock();

在 60 毫秒内运行

在 MATLAB 中:

abr = abs(randn(100000,50));
tic;abr=log2(abr);toc

经过的时间是 7.8 毫秒。

我可以理解 C++ 和 numpy 之间的速度差异,但 MATLAB 胜过一切。 我遇到过 http://fastapprox.googlecode.com/svn/trunk/fastapprox/src/fastonebigheader.h 但这只是浮动,而不是双精度,我不确定如何将其转换为双精度。

我也试过这个: http://hackage.haskell.org/package/approximate-0.2.2.1/src/cbits/fast.c 它具有快速的日志功能,当编译为 numpy ufunc 时,运行时间为 20 毫秒,这很棒,但准确性的损失很大。

关于如何实现 MATLAB 获得的神奇 log2 速度的任何想法?

更新

感谢大家的 cmets,这非常快速且非常有帮助!实际上,答案是并行化,即将负载分散到多个线程上。根据@morningsun 的建议,

%timeit numexpr.evaluate('log(drr)')

给出 5.6 毫秒,与 MATLAB 相当,谢谢! numexpr 已启用 MKL

【问题讨论】:

  • 向量化和并行化。
  • 这是一个典型的SIMD 场景。首先探索 C++ 代码的矢量化技术。例如,尝试OpenMP
  • C++ 编译器无法解开 log2() 调用的循环,因此它会花费大量时间来跟踪循环索引。正如 IKavanagh 所说,matlab 并行计算。您可以使用OpenMP 轻松做到这一点。
  • Python 模块 numexpr 对基准测试也很有趣。如果您可以启用 MKL (VML),它将即时执行 SIMD 和线程。它不直接做log2,所以使用log(a)/log(2.0)
  • @ElBrutale 由于获取日志是一个元素操作,您只需要存储在稀疏数组中的值,您可以通过其.data 属性轻松访问。

标签: python c++ matlab math numpy


【解决方案1】:

请注意,下面的所有都是 float32,而不是双精度。

更新: 我完全放弃了 gcc,转而使用英特尔的 icc。当性能至关重要并且您没有时间微调“编译器提示”以强制执行 gcc 矢量化时,这一切都会有所不同(参见,例如here

log_omp.c

GCC:gcc -o log_omp.so -fopenmp log_omp.c -lm -O3 -fPIC -shared -std=c99

ICC:icc -o log_omp.so -openmp loge_omp.c -lm -O3 -fPIC -shared -std=c99 -vec-report1 -xAVX -I/opt/intel/composer/mkl/include

#include <math.h>
#include "omp.h"
#include "mkl_vml.h"

#define restrict __restrict

inline void log_omp(int m, float * restrict a, float * restrict c);

void log_omp(int m, float * restrict a, float * restrict c)
{
   int i;
#pragma omp parallel for default(none) shared(m,a,c) private(i)
   for (i=0; i<m; i++) {
      a[i] = log(c[i]);
   }
}

// VML / icc only:
void log_VML(int m, float * restrict a, float * restrict c)
{
   int i;
   int split_to = 14;
   int iter = m / split_to;
   int additional = m % split_to;

//   vsLn(m, c, a);
#pragma omp parallel for default(none) shared(m,a,c, additional, iter) private(i) num_threads(split_to)
   for (i=0;i < (m-additional); i+=iter)
     vsLog10(iter,c+i,a+i);
     //vmsLn(iter,c+i,a+i, VML_HA);

   if (additional > 0)
     vsLog10(additional, c+m-additional, a+m-additional);
     //vmsLn(additional, c+m-additional, a+m-additional, VML_HA);
}

在 python 中:

from ctypes import CDLL, c_int, c_void_p
def log_omp(xs, out):
    lib = CDLL('./log_omp.so')
    lib.log_omp.argtypes = [c_int, np.ctypeslib.ndpointer(dtype=np.float32), np.ctypeslib.ndpointer(dtype=np.float32)]
    lib.log_omp.restype  = c_void_p
    n = xs.shape[0]
    out = np.empty(n, np.float32)
    lib.log_omp(n, out, xs)
    return out

Cython 代码(在 ipython 笔记本中,因此具有 %% 的魔力):

%%cython --compile-args=-fopenmp --link-args=-fopenmp
import  numpy as np
cimport numpy as np
from libc.math cimport log

from cython.parallel cimport prange
import cython

@cython.boundscheck(False)
def cylog(np.ndarray[np.float32_t, ndim=1] a not None,
        np.ndarray[np.float32_t, ndim=1] out=None):
    if out is None:
        out = np.empty((a.shape[0]), dtype=a.dtype)
    cdef Py_ssize_t i
    with nogil:
        for i in prange(a.shape[0]):
            out[i] = log(a[i])
    return out

时间安排:

numexpr.detect_number_of_cores() // 2
28

%env OMP_NUM_THREADS=28
x = np.abs(np.random.randn(50000000).astype('float32'))
y = x.copy()

# GCC
%timeit log_omp(x, y)
10 loops, best of 3: 21.6 ms per loop
# ICC
%timeit log_omp(x, y)
100 loops, best of 3: 9.6 ms per loop
%timeit log_VML(x, y)
100 loops, best of 3: 10 ms per loop

%timeit cylog(x, out=y)
10 loops, best of 3: 21.7 ms per loop

numexpr.set_num_threads(28)
%timeit out = numexpr.evaluate('log(x)')
100 loops, best of 3: 13 ms per loop

所以 numexpr 似乎比(糟糕的)编译的 gcc 代码做得更好,但 icc 胜出。

我发现一些有用且可耻地使用代码的资源来自:

http://people.duke.edu/~ccc14/sta-663/Optimization_Bakeoff.html

https://gist.github.com/zed/2051661

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-31
    • 2013-08-30
    • 2010-12-23
    • 2011-11-27
    • 2020-12-08
    相关资源
    最近更新 更多