main.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /*
  2. Copyright (C) 2010 Jochen Gerhard <gerhard@compeng.uni-frankfurt.de>
  3. Copyright (C) 2010-2015 Matthias Kretz <kretz@kde.org>
  4. Permission to use, copy, modify, and distribute this software
  5. and its documentation for any purpose and without fee is hereby
  6. granted, provided that the above copyright notice appear in all
  7. copies and that both that the copyright notice and this
  8. permission notice and warranty disclaimer appear in supporting
  9. documentation, and that the name of the author not be used in
  10. advertising or publicity pertaining to distribution of the
  11. software without specific, written prior permission.
  12. The author disclaim all warranties with regard to this
  13. software, including all implied warranties of merchantability
  14. and fitness. In no event shall the author be liable for any
  15. special, indirect or consequential damages or any damages
  16. whatsoever resulting from loss of use, data or profits, whether
  17. in an action of contract, negligence or other tortious action,
  18. arising out of or in connection with the use or performance of
  19. this software.
  20. */
  21. /*!
  22. Finite difference method example
  23. We calculate central differences for a given function and
  24. compare it to the analytical solution.
  25. */
  26. //! [includes]
  27. #include <Vc/Vc>
  28. #include <iostream>
  29. #include <iomanip>
  30. #include <cmath>
  31. #include "../tsc.h"
  32. using Vc::float_v;
  33. //! [includes]
  34. #define USE_SCALAR_SINCOS
  35. //! [constants]
  36. static constexpr std::size_t N = 10240000, PrintStep = 1000000;
  37. static constexpr float epsilon = 1e-7f;
  38. static constexpr float lower = 0.f;
  39. static constexpr float upper = 40000.f;
  40. static constexpr float h = (upper - lower) / N;
  41. //! [constants]
  42. //! [functions]
  43. // dfu is the derivative of fu. This is really easy for sine and cosine:
  44. static inline float fu(float x) { return ( std::sin(x) ); }
  45. static inline float dfu(float x) { return ( std::cos(x) ); }
  46. static inline Vc::float_v fu(Vc::float_v::AsArg x) {
  47. #ifdef USE_SCALAR_SINCOS
  48. Vc::float_v r;
  49. for (size_t i = 0; i < Vc::float_v::Size; ++i) {
  50. r[i] = std::sin(x[i]);
  51. }
  52. return r;
  53. #else
  54. return Vc::sin(x);
  55. #endif
  56. }
  57. static inline Vc::float_v dfu(Vc::float_v::AsArg x) {
  58. #ifdef USE_SCALAR_SINCOS
  59. Vc::float_v r;
  60. for (size_t i = 0; i < Vc::float_v::Size; ++i) {
  61. r[i] = std::cos(x[i]);
  62. }
  63. return r;
  64. #else
  65. return Vc::cos(x);
  66. #endif
  67. }
  68. //! [functions]
  69. // It is important for this example that the following variables (especially dy_points) are global
  70. // variables. Else the compiler can optimze all calculations of dy away except for the few places
  71. // where the value is used in printResults.
  72. Vc::Memory<float_v, N> x_points;
  73. Vc::Memory<float_v, N> y_points;
  74. float *Vc_RESTRICT dy_points;
  75. void printResults()
  76. {
  77. std::cout
  78. << "------------------------------------------------------------\n"
  79. << std::setw(15) << "fu(x_i)"
  80. << std::setw(15) << "FD fu'(x_i)"
  81. << std::setw(15) << "SYM fu'(x)"
  82. << std::setw(15) << "error %\n";
  83. for (std::size_t i = 0; i < N; i += PrintStep) {
  84. std::cout
  85. << std::setw(15) << y_points[i]
  86. << std::setw(15) << dy_points[i]
  87. << std::setw(15) << dfu(x_points[i])
  88. << std::setw(15) << std::abs((dy_points[i] - dfu(x_points[i])) / (dfu(x_points[i] + epsilon)) * 100)
  89. << "\n";
  90. }
  91. std::cout
  92. << std::setw(15) << y_points[N - 1]
  93. << std::setw(15) << dy_points[N - 1]
  94. << std::setw(15) << dfu(x_points[N - 1])
  95. << std::setw(15) << std::abs((dy_points[N - 1] - dfu(x_points[N - 1])) / (dfu(x_points[N - 1] + epsilon)) * 100)
  96. << std::endl;
  97. }
  98. int Vc_CDECL main()
  99. {
  100. {
  101. float_v x_i(Vc::IndexesFromZero);
  102. for ( unsigned int i = 0; i < x_points.vectorsCount(); ++i, x_i += float_v::Size ) {
  103. const float_v x = x_i * h;
  104. x_points.vector(i) = x;
  105. y_points.vector(i) = fu(x);
  106. }
  107. }
  108. dy_points = Vc::malloc<float, Vc::AlignOnVector>(N + float_v::Size - 1) + (float_v::Size - 1);
  109. double speedup;
  110. TimeStampCounter timer;
  111. { ///////// ignore this part - it only wakes up the CPU ////////////////////////////
  112. const float oneOver2h = 0.5f / h;
  113. // set borders explicit as up- or downdifferential
  114. dy_points[0] = (y_points[1] - y_points[0]) / h;
  115. // GCC auto-vectorizes the following loop. It is interesting to see that both Vc::Scalar and
  116. // Vc::SSE are faster, though.
  117. for (std::size_t i = 1; i < N - 1; ++i) {
  118. dy_points[i] = (y_points[i + 1] - y_points[i - 1]) * oneOver2h;
  119. }
  120. dy_points[N - 1] = (y_points[N - 1] - y_points[N - 2]) / h;
  121. } //////////////////////////////////////////////////////////////////////////////////
  122. {
  123. std::cout << "\n" << std::setw(60) << "Classical finite difference method" << std::endl;
  124. timer.start();
  125. const float oneOver2h = 0.5f / h;
  126. // set borders explicit as up- or downdifferential
  127. dy_points[0] = (y_points[1] - y_points[0]) / h;
  128. // GCC auto-vectorizes the following loop. It is interesting to see that both Vc::Scalar and
  129. // Vc::SSE are faster, though.
  130. for (std::size_t i = 1; i < N - 1; ++i) {
  131. dy_points[i] = (y_points[i + 1] - y_points[i - 1]) * oneOver2h;
  132. }
  133. dy_points[N - 1] = (y_points[N - 1] - y_points[N - 2]) / h;
  134. timer.stop();
  135. printResults();
  136. std::cout << "cycle count: " << timer.cycles()
  137. << " | " << static_cast<double>(N * 2) / timer.cycles() << " FLOP/cycle"
  138. << " | " << static_cast<double>(N * 2 * sizeof(float)) / timer.cycles() << " Byte/cycle"
  139. << "\n";
  140. }
  141. speedup = timer.cycles();
  142. {
  143. std::cout << std::setw(60) << "Vectorized finite difference method" << std::endl;
  144. timer.start();
  145. // All the differentials require to calculate (r - l) / 2h, where we calculate 1/2h as a
  146. // constant before the loop to avoid unnecessary calculations. Note that a good compiler can
  147. // already do this for you.
  148. const float_v oneOver2h = 0.5f / h;
  149. // Calculate the left border
  150. dy_points[0] = (y_points[1] - y_points[0]) / h;
  151. // Calculate the differentials streaming through the y and dy memory. The picture below
  152. // should give an idea of what values in y get read and what values are written to dy in
  153. // each iteration:
  154. //
  155. // y [...................................]
  156. // 00001111222233334444555566667777
  157. // 00001111222233334444555566667777
  158. // dy [...................................]
  159. // 00001111222233334444555566667777
  160. //
  161. // The loop is manually unrolled four times to improve instruction level parallelism and
  162. // prefetching on architectures where four vectors fill one cache line. (Note that this
  163. // unrolling breaks auto-vectorization of the Vc::Scalar implementation when compiling with
  164. // GCC.)
  165. // Use streaming stores to reduce the required memory bandwidth. Without streaming
  166. // stores the CPU would first have to load the cache line, where the store occurs, from
  167. // memory into L1, then overwrite the data, and finally write it back to memory. But
  168. // since we never actually need the data that the CPU fetched from memory we'd like to
  169. // keep that bandwidth free for real work. Streaming stores allow us to issue stores
  170. // which the CPU gathers in store buffers to form full cache lines, which then get
  171. // written back to memory directly without the costly read. Thus we make better use of
  172. // the available memory bandwidth.
  173. auto dy = Vc::makeIterator<float_v>(&dy_points[1], Vc::Streaming);
  174. // Prefetches make sure the data which is going to be used in the next iterations is already
  175. // in the L1 cache. The Vc::Prefetch<>() flag provides some sensible default for loops where no
  176. // elements are skipped. You can use Vc::Prefetch<L1, L2, Shared/Exclusive>() instead to set the stride of
  177. // L1 and L2 prefetches manually.
  178. auto y = y_points.begin(Vc::Prefetch<>());
  179. float_v y0 = *y++;
  180. const auto y_it_last = y_points.end();
  181. #ifdef Vc_ICC
  182. #pragma noprefetch
  183. #endif
  184. for (; y < y_it_last; y += 4 , dy += 4) {
  185. // calculate float_v::Size differentials per (left - right) / 2h
  186. float_v y1 = y[0];
  187. float_v y2 = y[1];
  188. float_v y3 = y[2];
  189. static_assert(float_v::Size >= 2, "This code requires a SIMD vector with at least two entries.");
  190. dy[0] = (y0.shifted(2, y1) - y0) * oneOver2h;
  191. y0 = y[3];
  192. dy[1] = (y1.shifted(2, y2) - y1) * oneOver2h;
  193. dy[2] = (y2.shifted(2, y3) - y2) * oneOver2h;
  194. dy[3] = (y3.shifted(2, y0) - y3) * oneOver2h;
  195. }
  196. /* Alternative:
  197. * use the STL transform algorithm. We have to rely on the compiler for unrolling, though.
  198. * At least ICC doesn't produce the best code from this...
  199. *
  200. std::transform(Vc::makeIterator(y_points.vector(1), Vc::Prefetch<>()),
  201. y_points.end(Vc::Prefetch<>()),
  202. Vc::makeIterator<float_v>(&dy_points[1], Vc::Streaming),
  203. [&y0,oneOver2h](float_v y1) -> float_v {
  204. const auto r = (y0.shifted(2, y1) - y0) * oneOver2h;
  205. y0 = y1;
  206. return r;
  207. });
  208. */
  209. // Process the last vector. Note that this works for any N because Vc::Memory adds padding
  210. // to y_points and dy_points such that the last scalar value is somewhere inside lastVector.
  211. // The correct right border value for dy_points is overwritten in the last step unless N is
  212. // a multiple of float_v::Size + 2.
  213. // y [...................................]
  214. // 8888
  215. // 8888
  216. // dy [...................................]
  217. // 8888
  218. {
  219. const size_t i = y_points.vectorsCount() - 1;
  220. const float_v left = y_points.vector(i, -2);
  221. const float_v right = y_points.lastVector();
  222. ((right - left) * oneOver2h).store(&dy_points[i * float_v::Size - 1], Vc::Unaligned);
  223. }
  224. // ... and finally the right border
  225. dy_points[N - 1] = (y_points[N - 1] - y_points[N - 2]) / h;
  226. timer.stop();
  227. printResults();
  228. std::cout << "cycle count: " << timer.cycles()
  229. << " | " << static_cast<double>(N * 2) / timer.cycles() << " FLOP/cycle"
  230. << " | " << static_cast<double>(N * 2 * sizeof(float)) / timer.cycles() << " Byte/cycle"
  231. << "\n";
  232. }
  233. speedup /= timer.cycles();
  234. std::cout << "Speedup: " << speedup << "\n";
  235. //! [cleanup]
  236. Vc::free(dy_points - float_v::Size + 1);
  237. return 0;
  238. }
  239. //! [cleanup]