Doc: Warn that product results depend on the detected cache sizes

libeigen/eigen!3440

Closes #3211

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Core/products/GeneralBlockPanelKernel.h b/Eigen/src/Core/products/GeneralBlockPanelKernel.h
index 6cf76f5..b29edc3 100644
--- a/Eigen/src/Core/products/GeneralBlockPanelKernel.h
+++ b/Eigen/src/Core/products/GeneralBlockPanelKernel.h
@@ -2733,7 +2733,7 @@
 }  // end namespace internal
 
 /** \returns the currently set level 1 cpu cache size (in bytes) used to estimate the ideal blocking size parameters.
- * \sa setCpuCacheSize */
+ * \sa setCpuCacheSizes */
 inline std::ptrdiff_t l1CacheSize() {
   std::ptrdiff_t l1, l2, l3;
   internal::manage_caching_sizes(GetAction, &l1, &l2, &l3);
@@ -2741,7 +2741,7 @@
 }
 
 /** \returns the currently set level 2 cpu cache size (in bytes) used to estimate the ideal blocking size parameters.
- * \sa setCpuCacheSize */
+ * \sa setCpuCacheSizes */
 inline std::ptrdiff_t l2CacheSize() {
   std::ptrdiff_t l1, l2, l3;
   internal::manage_caching_sizes(GetAction, &l1, &l2, &l3);
@@ -2749,7 +2749,7 @@
 }
 
 /** \returns the currently set level 3 cpu cache size (in bytes) used to estimate the ideal blocking size parameters.
- * \sa setCpuCacheSize */
+ * \sa setCpuCacheSizes */
 inline std::ptrdiff_t l3CacheSize() {
   std::ptrdiff_t l1, l2, l3;
   internal::manage_caching_sizes(GetAction, &l1, &l2, &l3);
@@ -2758,6 +2758,8 @@
 
 /** Set the cpu L1, L2 and L3 cache sizes (in bytes).
  * These values are used to adjust blocking sizes and, on some backends, the size crossovers between kernels.
+ * By default they are queried from the CPU that runs the first product. Setting them explicitly makes the
+ * blocking, and therefore the rounding of the results, independent of that CPU; see \ref TopicPitfalls_reproducibility.
  *
  * \sa computeProductBlockingSizes */
 inline void setCpuCacheSizes(std::ptrdiff_t l1, std::ptrdiff_t l2, std::ptrdiff_t l3) {
diff --git a/doc/Pitfalls.dox b/doc/Pitfalls.dox
index b3f49bc..c8434e3 100644
--- a/doc/Pitfalls.dox
+++ b/doc/Pitfalls.dox
@@ -146,5 +146,24 @@
 For coefficient-wise Boolean negation use <tt>!A.array()</tt>; for coefficient-wise XOR use
 <tt>A.array() != B.array()</tt>.
 
+\section TopicPitfalls_reproducibility Bitwise reproducibility of products
+
+%Eigen does not promise bitwise identical floating-point results across machines, builds, or runs. Matrix
+products, triangular solves, and the decompositions built on them split the summation into blocks sized to the
+CPU caches, and floating-point addition is not associative, so a different blocking can change the last bits of
+the result. The cache sizes are queried once, on the thread that runs the first such operation, and are kept for
+the life of the process. On CPUs that mix core types with different caches, such as Intel processors with
+P-cores and E-cores, the results of a program can therefore depend on which kind of core happened to run its
+first product. The number of threads and the instruction set enabled at compile time change the results in the
+same way.
+
+Where bitwise reproducibility matters, either:
+  - pin the process to one core type, for example with \c taskset on Linux or <tt>start /affinity</tt> on Windows,
+    or
+  - call \c Eigen::setCpuCacheSizes() with fixed values before the first product, so that the blocking no longer
+    depends on the core that is running.
+
+In both cases, also fix the number of threads and the build configuration.
+
 */
 }