{"id":1242,"date":"2023-07-13T13:15:06","date_gmt":"2023-07-13T11:15:06","guid":{"rendered":"https:\/\/intsight.com\/?p=1242"},"modified":"2023-07-20T18:50:18","modified_gmt":"2023-07-20T16:50:18","slug":"la-la-la-la-life-goes-on","status":"publish","type":"post","link":"https:\/\/intsight.com\/index.php\/2023\/07\/13\/la-la-la-la-life-goes-on\/","title":{"rendered":"La la, how their life goes on"},"content":{"rendered":"<p><span style=\"font-variant: small-caps; font-size: 105%\">Si insisto tanto<\/span> en estos temas de instrucciones vectoriales, es parar preparar a mis lectores potenciales para que usen estas t\u00e9cnicas cuando lo crean necesario. Naturalmente, las primeras explicaciones van a ser del tipo <em>heroico<\/em>: todo hay que hacerlo a mano, razonando desde los primeros principios. Pero, como dec\u00eda <a href=\"https:\/\/www.thebeatles.com\/ob-la-di-ob-la-da\" rel=\"noopener\" target=\"_blank\">Molly Jones<\/a>, la mujer de Desmond Jones, <em>la la, how their life goes on<\/em>: la vida sigue su curso, y lo normal en la vida de un programador es usar las t\u00e9cnicas que todos conocemos y apreciamos para ahorrarnos esfuerzo.<\/p>\n<p>Lo primero para que todos nos ahorremos trabajo, por supuesto, consiste en usar estas cosas a trav\u00e9s de una librer\u00eda bien probada. Ya existen unas cuantas de estas, pero estoy creando Austra porque hay un nicho muy concreto, y porque la librer\u00eda tiene m\u00e9ritos propios. La idea es hacer de Austra un proyecto open source, por supuesto. En este momento est\u00e1 en GitHub, pero no es por ahora un repositorio p\u00fablico porque la aplicaci\u00f3n de \u00abpruebas\u00bb est\u00e1 basada en WPF y DevExpress. O me compro personalmente una licencia comercial, o cambio los controles por algo gratuito. No es sencillo. Hay cosas como Avalonia o Uno Platform, que adem\u00e1s, son multiplataformas, pero son bastante pobres en componentes. Y no quiero ni o\u00edr hablar de .NET MAUI: es un fracaso, de momento. Antes que usar .NET MAUI, prefiero \u00abdegradar\u00bb la aplicaci\u00f3n a Windows Forms (tengo un editor de c\u00f3digo bastante bueno) y currarme los gr\u00e1ficos y las cosas que falten. Pero a eso vamos: a que Austra est\u00e9 disponible gratuitamente como open source, que cualquier que quiera pueda colaborar, que haya un conjunto de tests y benchmarks exhaustivo, etc, etc.<\/p>\n<p>Pero no se escribe un post para explicar lo obvio. Mi verdadero objetivo ahora es explicar un par de t\u00e9cnicas muy tontas que bajan el list\u00f3n del <em>hero\u00edsmo<\/em> al escribir este tipo de c\u00f3digo, y que todos conocemos, pero que nos puede dar miedo usar a primeras, porque no sabes c\u00f3mo va a interferir en la generaci\u00f3n de c\u00f3digo de .NET 7 y .NET 8 (que tiene sus cagadas, como todo).<\/p>\n<p>Primer ejemplo: resulta que mucho de estos algoritmos algebraicos calculan el producto escalar de dos zonas de memoria en muchos casos. Hay una clase <code>Vector<\/code> con su correspondiente producto escalar, e incluso un <code>ComplexVector<\/code>, pero es mejor tener una rutina que maneje directamente zonas de memoria con un tama\u00f1o predeterminado. Austra tiene una clase est\u00e1tica <code>CommonMatrix<\/code> que precisamente implementa estas cosas. \u00c9sta es la \u00faltima versi\u00f3n del m\u00e9todo en cuesti\u00f3n:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">\n\/\/\/ &lt;summary&gt;\n\/\/\/ Computes the dot product of two double arrays.\n\/\/\/ &lt;\/summary&gt;\n\/\/\/ &lt;param name=\"p\"&gt;Pointer to the first array.&lt;\/param&gt;\n\/\/\/ &lt;param name=\"q\"&gt;Pointer to the second array.&lt;\/param&gt;\n\/\/\/ &lt;param name=\"size\"&gt;Number of items in each array.&lt;\/param&gt;\n\/\/\/ &lt;returns&gt;A sum of products.&lt;\/returns&gt;\n[MethodImpl(MethodImplOptions.AggressiveInlining)]\npublic unsafe static double DotProduct(\n    double* p, double* q, int size)\n{\n    double sum;\n    int i = 0;\n    if (Avx.IsSupported)\n    {\n        Vector256&lt;double&gt; acc = Vector256&lt;double&gt;.Zero;\n        for (int top = size & AVX_MASK; i &lt; top; i += 4)\n            acc = acc.MultiplyAdd(p + i, q + i);\n        sum = acc.Sum();\n    }\n    else\n        sum = 0;\n    for (; i &lt; size; i++)\n        sum += p[i] * q[i];\n    return sum;\n}<\/pre>\n<p>A primera vista, lo \u00fanico raro aqu\u00ed es el atributo que fuerza un <em>inlining<\/em> agresivo. Pero hay un par de cosillas m\u00e1s que no son m\u00e9todos habituales de AVX. Los muestro aparte aqu\u00ed, porque son m\u00e9todos de extensi\u00f3n de la misma clase <code>CommonMatrix<\/code>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">\n\/\/\/ &lt;summary&gt;Sums all the elements in a vector.&lt;\/summary&gt;\n\/\/\/ &lt;param name=\"v\"&gt;\n\/\/\/ A intrinsics vector with four doubles.\n\/\/\/ &lt;\/param&gt;\n\/\/\/ &lt;returns&gt;The total value.&lt;\/returns&gt;\n[MethodImpl(MethodImplOptions.AggressiveInlining)]\ninternal static double Sum(this Vector256&lt;double&gt; v)\n{ \n    v = Avx.HorizontalAdd(v, v);\n    return v.ToScalar() + v.GetElement(2);\n}<\/pre>\n<p>Resulta que sumar los cuatro elementos de un vector de cuatro dobles no es moco de pavo. Hay teor\u00edas que pululan por la Internet sobre cu\u00e1l es la forma eficiente de lograrlo. Yo no estoy seguro a\u00fan de que la m\u00eda sea la m\u00e1s eficiente, pero gracias al encapsulamiento en un m\u00e9todo separado, si descubro algo mejor, tendr\u00e9 que tocar el c\u00f3digo en un \u00fanico lugar. Obvio, \u00bfno? Pero hasta comprobar que el JIT de .NET Core no se volv\u00eda loco, no me atrev\u00ed a dar este paso. Ya he comprobado que no pasan cosas raras.<\/p>\n<p>Es curioso, sin embargo, que el m\u00ednimo y el m\u00e1ximo de un vector se calcule m\u00e1s eficientemente con un m\u00e9todo como el siguiente:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">\n[MethodImpl(MethodImplOptions.AggressiveInlining)]\ninternal static double Max(this Vector256&lt;double&gt; v)\n{\n    var x = Sse2.Max(v.GetLower(), v.GetUpper());\n    return Math.Max(x.ToScalar(), x.GetElement(1));\n}<\/pre>\n<p>En este caso, no parece que la transici\u00f3n temporal a una instrucci\u00f3n SSE haga mucho da\u00f1o al c\u00f3digo que la rodea. De todas maneras, observe que para la reducci\u00f3n final al escalar hay que ir a por el segundo elemento directamente. Cosas de Intel.<\/p>\n<p>\u00c9ste es otro m\u00e9todo que usa el c\u00f3digo original, omitiendo esta vez los comentarios XML:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">\ninternal unsafe static Vector256&lt;double&gt; MultiplyAdd(\n    this Vector256&lt;double&gt; summand,\n    double* multiplicand,\n    double* multiplier) =&gt;\n    Fma.IsSupported\n        ? Fma.MultiplyAdd(\n            Avx.LoadVector256(multiplicand),\n            Avx.LoadVector256(multiplier),\n            summand)\n        : Avx.Add(summand, Avx.Multiply(\n            Avx.LoadVector256(multiplicand),\n            Avx.LoadVector256(multiplier)));<\/pre>\n<p>Hay un par de variantes m\u00e1s que usan vectores directamente en vez de direcciones, y variantes adicionales para <code>MultiplySubtract<\/code> y <code>MultiplyAddNegated<\/code>, que sons m\u00e9todos <code>FMA<\/code>. Estas aparentes tonter\u00edas ahorran l\u00edneas y l\u00edneas de c\u00f3digo. En mi caso, como tengo memoria fotogr\u00e1fica, prefiero que el c\u00f3digo fuente sea lo m\u00e1s peque\u00f1o posible para poder recordarlo m\u00e1s adelante.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Si insisto tanto en estos temas de instrucciones vectoriales, es parar preparar a mis lectores potenciales para que usen estas t\u00e9cnicas cuando lo crean necesario. Naturalmente, las primeras explicaciones van a ser del tipo heroico: todo hay que hacerlo a mano, razonando desde los primeros principios. Pero, como dec\u00eda Molly Jones, la mujer de Desmond [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1241,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[65,15,73,75,23],"class_list":["post-1242","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-c","tag-net","tag-algorithms","tag-austra","tag-modularidad","tag-simd"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/intsight.com\/wp-content\/uploads\/2023\/07\/lifegoeson.png?fit=448%2C450&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts\/1242","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/comments?post=1242"}],"version-history":[{"count":19,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts\/1242\/revisions"}],"predecessor-version":[{"id":1261,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/posts\/1242\/revisions\/1261"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/media\/1241"}],"wp:attachment":[{"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/media?parent=1242"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/categories?post=1242"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/intsight.com\/index.php\/wp-json\/wp\/v2\/tags?post=1242"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}