{"id":33793,"date":"2019-10-31T21:54:42","date_gmt":"2019-10-31T18:54:42","guid":{"rendered":"https:\/\/prohoster.info\/blog\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti\/"},"modified":"2019-10-31T21:54:42","modified_gmt":"2019-10-31T18:54:42","slug":"bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","status":"publish","type":"post","link":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","title":{"rendered":"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/86ef928e6741022b2c0e5885a031408a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h2>Palabras Introductorias<\/h2>\n<p>\nPresent\u00e9 esta charla en ingl\u00e9s en la conferencia GopherCon Rusia 2019 en Mosc\u00fa y en ruso en un meetup en Nizhni N\u00f3vgorod. Se trata del \u00edndice bitmap, menos com\u00fan que el B-tree, pero igualmente interesante. Estoy compartiendo <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/WvlUH6MjUuI?list=PL3xVZC4USRNSO_kb2lh_J_no6C-KJ7Phg\">la grabaci\u00f3n<\/a><\/noindex> de la charla en la conferencia en ingl\u00e9s y la transcripci\u00f3n en texto en ruso.<\/p>\n<p>Exploraremos c\u00f3mo funciona el \u00edndice bitmap, cu\u00e1ndo es mejor y cu\u00e1ndo es peor que otros \u00edndices y en qu\u00e9 casos es significativamente m\u00e1s r\u00e1pido; veremos en qu\u00e9 populares SGBD ya existen \u00edndices bitmap; intentaremos escribir el nuestro en Go. Y como 'postre', utilizaremos bibliotecas listas para crear nuestra base de datos especializada s\u00faper r\u00e1pida.<\/p>\n<p>Espero sinceramente que mis esfuerzos sean \u00fatiles e interesantes para ustedes. \u00a1Vamos a ello!<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Introducci\u00f3n<\/h2>\n<p>\n<center><div class=\"youtube-placeholder\" data-id=\"WvlUH6MjUuI\" onclick=\"loadVideo(this)\">\r\n        <img decoding=\"async\" src=\"https:\/\/img.youtube.com\/vi\/WvlUH6MjUuI\/hqdefault.jpg\" alt=\"Reproducir video\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\n<noindex><a rel=\"nofollow\" href=\"http:\/\/bit.ly\/bitmapindexes\">http:\/\/bit.ly\/bitmapindexes<\/a><\/noindex><br \/>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/mkevac\/gopherconrussia2019\">https:\/\/github.com\/mkevac\/gopherconrussia2019<\/a><\/noindex><\/p>\n<p>\u00a1Hola a todos! Son las seis de la tarde y todos estamos s\u00faper cansados. Es un momento maravilloso para hablar sobre la aburrida teor\u00eda de \u00edndices de bases de datos, \u00bfverdad? No se preocupen, tendr\u00e9 un par de l\u00edneas de c\u00f3digo fuente aqu\u00ed y all\u00e1. \ud83d\ude42<\/p>\n<p>Sin bromas, la charla est\u00e1 llena de informaci\u00f3n y no tenemos tanto tiempo. As\u00ed que, \u00a1empecemos!<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/e778e13727700f0335a4b5558a0d8db3.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHoy hablar\u00e9 sobre lo siguiente:<\/p>\n<ul>\n<li>\u00bfQu\u00e9 son los \u00edndices;\n<\/li>\n<li>\u00bfQu\u00e9 es un \u00edndice bitmap;\n<\/li>\n<li>\u00bfD\u00f3nde se utiliza y d\u00f3nde NO se utiliza y por qu\u00e9;\n<\/li>\n<li>una implementaci\u00f3n simple en Go y un poco de lucha con el compilador;\n<\/li>\n<li>una implementaci\u00f3n un poco menos simple, pero mucho m\u00e1s eficiente en ensamblador Go;\n<\/li>\n<li>los 'problemas' de los \u00edndices bitmap;\n<\/li>\n<li>implementaciones existentes.\n<\/li>\n<\/ul>\n<h2>\u00bfQu\u00e9 son los \u00edndices?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/b80e5b990c44814afe9150a9a82351fd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nUn \u00edndice es una estructura de datos separada que mantenemos y actualizamos adem\u00e1s de los datos principales. Se utiliza para acelerar la b\u00fasqueda. Sin \u00edndices, la b\u00fasqueda requerir\u00eda un recorrido completo de los datos (un proceso llamado 'b\u00fasqueda completa'), y este proceso tiene una complejidad algor\u00edtmica lineal. Pero las bases de datos suelen contener una enorme cantidad de datos y la complejidad lineal es demasiado lenta. Idealmente, nos gustar\u00eda obtener una complejidad logar\u00edtmica o constante.<\/p>\n<p>Este es un tema enorme y complejo, lleno de matices y compromisos, pero despu\u00e9s de observar d\u00e9cadas de desarrollo e investigaci\u00f3n en diferentes bases de datos, estoy dispuesto a afirmar que existen solo unos pocos enfoques ampliamente utilizados para crear \u00edndices de bases de datos.<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/08a74dbb365035cd99bc94d72644a1b7.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl primer enfoque consiste en reducir jer\u00e1rquicamente el \u00e1rea de b\u00fasqueda, dividiendo el \u00e1rea de b\u00fasqueda en partes m\u00e1s peque\u00f1as.<\/p>\n<p>Normalmente, hacemos esto utilizando diferentes tipos de \u00e1rboles. Un ejemplo podr\u00eda ser una gran caja con materiales en su armario, donde hay cajas m\u00e1s peque\u00f1as con materiales divididos por diferentes tem\u00e1ticas. Si necesitas materiales, seguramente buscar\u00e1s en la caja etiquetada como 'Materiales', y no en la que dice 'Galletas', \u00bfverdad?<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/423fac47c748980b18af434644af4dae.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl segundo enfoque consiste en identificar de inmediato el elemento o grupo de elementos que se necesita. Hacemos esto en hash maps o en \u00edndices inversos. El uso de hash maps es muy parecido al ejemplo anterior, solo que en lugar de una caja con cajas tienes en tu armario un mont\u00f3n de peque\u00f1as cajas con art\u00edculos finales.<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/088fed0a2a0feea4a23edbf0ca654805.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl tercer enfoque es eliminar la necesidad de b\u00fasqueda. Esto lo hacemos con mediante filtros de Bloom o filtros cuckoo. Los primeros dan una respuesta instant\u00e1nea, liber\u00e1ndote de la necesidad de buscar.<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/493bc4f20baacc0a5dc0faf15cc46285.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl \u00faltimo enfoque se basa en aprovechar al m\u00e1ximo todo el poder que nos ofrece el hardware moderno. Es lo que hacemos en \u00edndices bitmap. S\u00ed, al usarlos a veces necesitamos recorrer todo el \u00edndice, pero lo hacemos de manera s\u00faper eficiente.<\/p>\n<p>Como ya mencion\u00e9, el tema de los \u00edndices de bases de datos es amplio y est\u00e1 lleno de compromisos. Esto significa que a veces podemos utilizar varios enfoques al mismo tiempo: si necesitamos acelerar a\u00fan m\u00e1s la b\u00fasqueda o si es necesario cubrir todos los posibles tipos de b\u00fasqueda.<\/p>\n<p>Hoy les hablar\u00e9 sobre el enfoque menos conocido de los mencionados: los \u00edndices bitmap.<\/p>\n<h2>\u00bfQui\u00e9n soy yo para hablar sobre este tema?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/42a9d17a507c392bc202254a92dfaf41.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSoy el l\u00edder de equipo en Badoo (quiz\u00e1s te suene m\u00e1s otro de nuestros productos: Bumble). Ya tenemos m\u00e1s de 400 millones de usuarios en todo el mundo y muchas funciones que se encargan de encontrarles la mejor pareja. Lo hacemos a trav\u00e9s de servicios personalizados, que utilizan tambi\u00e9n \u00edndices bitmap.<\/p>\n<h2>\u00bfQu\u00e9 es entonces un \u00edndice bitmap?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/14f20f3697a20c02f6b3510dc7f0ae4d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nLos \u00edndices de bitmap, como su nombre indica, utilizan bitmaps o conjuntos de bits para implementar un \u00edndice de b\u00fasqueda. Desde una vista general, este \u00edndice consiste en uno o varios de estos bitmaps, que representan entidades (como personas) y sus propiedades o par\u00e1metros (edad, color de ojos, etc.), y un algoritmo que utiliza operaciones binarias (AND, OR, NOT) para responder a la consulta de b\u00fasqueda.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/f815720330a51b1f0798e45d23160d3b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSe nos dice que los \u00edndices de bitmap son especialmente adecuados y muy eficientes para casos en los que hay b\u00fasquedas que combinan consultas en muchas columnas de baja cardinalidad (imagina 'color de ojos' o 'estado civil' frente a algo como 'distancia al centro de la ciudad'). Pero m\u00e1s adelante demostrar\u00e9 que tambi\u00e9n funcionan muy bien en columnas de alta cardinalidad.<\/p>\n<p>Veamos un ejemplo b\u00e1sico de un \u00edndice de bitmap.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/33fe23476e0931c10345d7175b83d68b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nImagina que tenemos una lista de restaurantes en Mosc\u00fa con propiedades binarias como estas:<\/p>\n<ul>\n<li>cerca del metro (near metro);\n<\/li>\n<li>tiene estacionamiento privado (has private parking);\n<\/li>\n<li>tiene terraza (has terrace);\n<\/li>\n<li>aceptan reservas (accepts reservations);\n<\/li>\n<li>apto para vegetarianos (vegan friendly);\n<\/li>\n<li>caro (expensive).\n<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/fcbad539ea12f79a9d06966ce308637e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAsignemos a cada restaurante un n\u00famero secuencial comenzando desde 0 y reservemos memoria para 6 bitmaps (uno para cada caracter\u00edstica). Luego, llenaremos estos bitmaps dependiendo de si el restaurante tiene o no dicha propiedad. Si el restaurante 4 tiene terraza, entonces el bit n\u00ba4 en el bitmap 'tiene terraza' se establecer\u00e1 en 1 (si no hay terraza, en 0).<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/3709c426617d4364f392ee6b68f92b00.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAhora tenemos el \u00edndice de bitmap m\u00e1s simple que se puede imaginar, y podemos usarlo para responder a consultas como:<\/p>\n<ul>\n<li>\u00abMu\u00e9strame los restaurantes aptos para vegetarianos\u00bb;\n<\/li>\n<li>\u00abMu\u00e9strame restaurantes econ\u00f3micos con terraza, donde se pueda reservar mesa\u00bb.\n<\/li>\n<\/ul>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/90acb0f890686bd52c3db1fc667f0b0b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/0fd0b7fe9f7b7039022ad5c79fe873bc.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u00bfC\u00f3mo? Ve\u00e1moslo. La primera consulta es muy sencilla. Todo lo que necesitamos hacer es tomar el bitmap 'apto para vegetarianos' y convertirlo en una lista de restaurantes cuyas bits est\u00e1n activadas.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/ef4a5cfd658ff4ef8bc0c638c4522d11.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/9cc175bae55c16018fdf5ff95517a61a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nLa segunda consulta es un poco m\u00e1s complicada. Necesitamos aplicar la operaci\u00f3n bit a bit NOT al bitmap \"caro\" para obtener una lista de restaurantes econ\u00f3micos, luego hacer un AND con el bitmap \"se puede reservar\" y AND tambi\u00e9n el resultado con el bitmap \"tiene veranda\". El bitmap resultante contendr\u00e1 una lista de establecimientos que cumplen con todos nuestros criterios. En este ejemplo, solo hay un restaurante: \"Juventud\".<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/f1cdda0cbf7f15278553899cf876c17e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/126b50e0f622b6e36c461cd74e708c38.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAqu\u00ed hay mucha teor\u00eda, pero no se preocupen, veremos el c\u00f3digo muy pronto.<\/p>\n<h2>\u00bfD\u00f3nde se utilizan los \u00edndices de bitmap?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/406132236c71a4f66ae79957b6e633b3.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSi \"googleas\" \u00edndices de bitmap, el 90% de las respuestas estar\u00e1n relacionadas de alguna manera con Oracle DB. Pero, \u00bfotras bases de datos tambi\u00e9n apoyan esta caracter\u00edstica tan genial, verdad? No del todo. <\/p>\n<p>Hagamos un recorrido por la lista de los principales sospechosos.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/d18d66451a8a26b0ddf121f8ec0204cd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nMySQL a\u00fan no soporta \u00edndices de bitmap, pero hay una propuesta para agregar esta opci\u00f3n (<noindex><a rel=\"nofollow\" href=\"https:\/\/dev.mysql.com\/worklog\/task\/?id=1524\">https:\/\/dev.mysql.com\/worklog\/task\/?id=1524<\/a><\/noindex>).<\/p>\n<p>PostgreSQL no soporta \u00edndices de bitmap, pero utiliza simples bitmaps y operaciones bit a bit para combinar resultados de b\u00fasqueda de varios \u00edndices diferentes.<\/p>\n<p>Tarantool tiene \u00edndices de bitset, que permiten una b\u00fasqueda simple.<\/p>\n<p>Redis tiene campos bit simples<noindex><a rel=\"nofollow\" href=\"https:\/\/redis.io\/commands\/bitfield\"> (https:\/\/redis.io\/commands\/bitfield<\/a><\/noindex>) sin capacidad de b\u00fasqueda sobre ellos.<\/p>\n<p>MongoDB a\u00fan no soporta \u00edndices de bitmap, pero tambi\u00e9n hay una propuesta para agregar esta opci\u00f3n <noindex><a rel=\"nofollow\" href=\"https:\/\/jira.mongodb.org\/browse\/SERVER-1723\">https:\/\/jira.mongodb.org\/browse\/SERVER-1723<\/a><\/noindex><\/p>\n<p>Elasticsearch utiliza bitmaps internamente<noindex><a rel=\"nofollow\" href=\"https:\/\/www.elastic.co\/blog\/frame-of-reference-and-roaring-bitmaps\"> (https:\/\/www.elastic.co\/blog\/frame-of-reference-and-roaring-bitmaps<\/a><\/noindex>).<\/p>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/232d335963603d6e6dec98839fc9f486.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ul>\n<li>Pero en nuestro vecindario ha llegado un nuevo vecino: Pilosa. Esta es una nueva base de datos no relacional, escrita en Go. Solo contiene \u00edndices de bitmap y se basa completamente en ellos. Hablaremos de esto un poco m\u00e1s tarde.\n<\/li>\n<\/ul>\n<h2>Implementaci\u00f3n en Go<\/h2>\n<p>\nPero, \u00bfpor qu\u00e9 los \u00edndices de bitmap se utilizan tan raramente? Antes de responder a esta pregunta, me gustar\u00eda mostrarles una implementaci\u00f3n de un \u00edndice de bitmap muy simple en Go.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/ae7f4c1a4a740fe709b05dfaee27ac9b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nLos bitmaps, en esencia, se representan como simples bloques de datos. En Go, usemos para esto slices de bytes.<\/p>\n<p>Tenemos un bitmap para una caracter\u00edstica del restaurante, y cada bit en el bitmap indica si un restaurante espec\u00edfico tiene o no esa propiedad.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/1ab628ee15887d6b3c6c99855aa610c8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNecesitaremos dos funciones auxiliares. Una se usar\u00e1 para llenar nuestros mapas de bits con datos aleatorios. Aleatorios, pero con una probabilidad espec\u00edfica de que un restaurante tenga cada atributo. Por ejemplo, creo que en Mosc\u00fa hay muy pocos restaurantes en los que no se pueda reservar una mesa, y me parece que aproximadamente el 20% de los establecimientos son aptos para vegetarianos.<\/p>\n<p>La segunda funci\u00f3n convertir\u00e1 el mapa de bits en una lista de restaurantes.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/ab9c87a0ba63750116e2e7968f842a9a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/498cf7a33611d99b90197d4ee82e2834.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPara responder a la consulta \"Mu\u00e9strame restaurantes econ\u00f3micos que tengan una terraza y donde se pueda reservar una mesa\", necesitaremos dos operaciones de bits: NOT y AND.<\/p>\n<p>Podemos simplificar un poco nuestro c\u00f3digo utilizando una operaci\u00f3n m\u00e1s compleja AND NOT.<\/p>\n<p>Tenemos funciones para cada una de estas operaciones. Ambas recorren los slices, toman los elementos correspondientes de cada uno, combinan con la operaci\u00f3n de bits y colocan el resultado en el slice resultante.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/ec5652a34f0370dfb03df199f341f153.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nY ahora podemos utilizar nuestros mapas de bits y funciones para responder a la consulta de b\u00fasqueda.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/cf9500ccfe75995a6008191c16729689.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEl rendimiento no es tan alto, a pesar de que las funciones son muy simples y hemos ahorrado bastante al no devolver un nuevo slice resultante en cada llamada a la funci\u00f3n.<\/p>\n<p>Al perfilar un poco con pprof, not\u00e9 que el compilador de Go omiti\u00f3 una optimizaci\u00f3n muy sencilla pero muy importante: la inlining de funciones.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/9092f5ba3940d0a4f3fbfa90b364d716.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEl problema es que el compilador de Go tiene un gran temor a los bucles que recorren slices y se niega a inlinear funciones que contienen tales bucles.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/d1bddd62b61b367dd1f680f223fa0ce7.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPero yo no tengo miedo y puedo enga\u00f1ar al compilador utilizando goto en lugar de un bucle, como en los viejos tiempos.<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/5b1c3cb26b923972686047910ecd1c31.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/1ca31d12dc90931b674c6a86a4ea23bd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nY, como pueden ver, ahora el compilador est\u00e1 encantado de inlinear nuestra funci\u00f3n. Al final, logramos ahorrar alrededor de 2 microsegundos. \u00a1No est\u00e1 mal!<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/bdd6735d16082573e600bffe2cdd5662.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl segundo cuello de botella no es dif\u00edcil de ver si observas atentamente la salida del ensamblador. El compilador agreg\u00f3 una verificaci\u00f3n de l\u00edmites de slice justo dentro de nuestro bucle m\u00e1s caliente. El hecho es que Go es un lenguaje seguro, el compilador teme que mis tres argumentos (tres slices) tengan diferentes tama\u00f1os. Te\u00f3ricamente, eso generar\u00eda la posibilidad de un desbordamiento de b\u00fafer (buffer overflow).<\/p>\n<p>Calmemos al compilador mostr\u00e1ndole que todos los slices tienen el mismo tama\u00f1o. Podemos hacer esto a\u00f1adiendo una simple verificaci\u00f3n al inicio de nuestra funci\u00f3n.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/59b4ce687a11dd653e9f12fc3130d89a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAl ver esto, el compilador felizmente omite la verificaci\u00f3n, y al final ahorramos 500 nanosegundos m\u00e1s.<\/p>\n<h2>Lotes grandes<\/h2>\n<p>\nBien, hemos logrado obtener algo de rendimiento de nuestra simple implementaci\u00f3n, pero este resultado, en realidad, es mucho peor de lo que podr\u00eda ser con el hardware actual.<\/p>\n<p>Todo lo que hacemos son operaciones b\u00e1sicas con bits, y nuestros procesadores las ejecutan de manera muy eficiente. Pero, desafortunadamente, estamos 'alimentando' a nuestro procesador con trozos de trabajo muy peque\u00f1os. Nuestras funciones ejecutan operaciones byte a byte. Podemos ajustar muy f\u00e1cilmente nuestro c\u00f3digo para que trabaje con trozos de 8 bytes, utilizando slices de UInt64.<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/8d1b60ba4b7046c836601631cadd0b6a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nComo pueden ver, este peque\u00f1o cambio aceler\u00f3 nuestra programa ocho veces al aumentar el tama\u00f1o del lote ocho veces. La mejora se podr\u00eda considerar lineal.<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/0fc663412d04dd38b927de5c8776f69d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Implementaci\u00f3n en ensamblador<\/h2>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/b3ef133167b89da983356b1c7389aecd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPero esto no es el final. Nuestros procesadores pueden trabajar con trozos de 16, 32 e incluso 64 bytes. Estas operaciones 'anchas' se llaman single instruction multiple data (SIMD; una instrucci\u00f3n, muchos datos), y el proceso de transformar el c\u00f3digo de tal manera que utilice estas operaciones se llama vectorizaci\u00f3n.<\/p>\n<p>Desafortunadamente, el compilador de Go no es particularmente bueno en la vectorizaci\u00f3n. En este momento, la \u00fanica forma de vectorizar c\u00f3digo en Go es tomar y escribir manualmente las operaciones de datos utilizando ensamblador de Go.<\/p>\n<p><img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/91278be45df67e1f9572d68fab7ebad1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEl ensamblador de Go es una criatura extra\u00f1a. Seguramente saben que el ensamblador es algo que est\u00e1 muy vinculado a la arquitectura de la computadora para la que est\u00e1n programando, pero en Go no es as\u00ed. El ensamblador de Go se parece m\u00e1s a un IRL (intermediate representation language) o lenguaje de representaci\u00f3n intermedia: es pr\u00e1cticamente independiente de la plataforma. Rob Pike hizo una excelente <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=KINIAgRpkDA\">informe<\/a><\/noindex> charla al respecto hace unos a\u00f1os en GopherCon en Denver.<\/p>\n<p>Adem\u00e1s, Go utiliza un formato inusual llamado Plan 9, que difiere de los formatos reconocidos de AT&amp;T e Intel.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/526f75eb18edfc2f851f2725e9d6f69e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSe puede decir con confianza que escribir ensamblador de Go a mano no es la actividad m\u00e1s divertida.<\/p>\n<p>Pero, afortunadamente, ya hay dos herramientas de alto nivel que nos ayudan en la escritura de ensamblador de Go: PeachPy y avo. Ambas utilidades generan ensamblador de Go a partir de c\u00f3digo de nivel m\u00e1s alto escrito en Python y Go, respectivamente.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/b8a7aa2c585b805e9b1f2ada186b84e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEstas utilidades simplifican cosas como la asignaci\u00f3n de registros (selecci\u00f3n de registros de procesador), la escritura de bucles y, en general, facilitan el proceso de entrada en el mundo de la programaci\u00f3n en ensamblador en Go.<\/p>\n<p>Vamos a utilizar avo, as\u00ed que nuestros programas ser\u00e1n casi programas normales en Go.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/128e4adef14e5f2cf00fb5b6302ef58e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEste es el ejemplo m\u00e1s simple de un programa en avo. Tenemos una funci\u00f3n main() que define dentro de s\u00ed misma una funci\u00f3n Add(), que se encarga de sumar dos n\u00fameros. Aqu\u00ed hay funciones auxiliares para obtener par\u00e1metros por nombre y conseguir uno de los registros de procesador libres y adecuados. Cada operaci\u00f3n de procesador tiene una funci\u00f3n correspondiente en avo, como se puede ver en ADDQ. Y, finalmente, vemos una funci\u00f3n auxiliar para guardar el valor resultante.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/01ccaa6aa6d394ef598ea2dbc9257d87.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAl ejecutar go generate, ejecutaremos el programa en avo y se generar\u00e1n dos archivos:<\/p>\n<ul>\n<li>add.s con el c\u00f3digo resultante en ensamblador Go;\n<\/li>\n<li>stub.go con las cabeceras de funciones para conectar dos mundos: Go y ensamblador.\n<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/72a9443776ecf45eef6fb97a4e08acba.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAhora que hemos visto qu\u00e9 hace y c\u00f3mo funciona avo, echemos un vistazo a nuestras funciones. He implementado versiones tanto escalares como vectoriales (SIMD) de las funciones.<\/p>\n<p>Primero, echemos un vistazo a las versiones escalares.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/ec31dbb8b97b9d7c1012a120fa18cdaf.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAl igual que en el ejemplo anterior, solicitamos un registro de uso general libre y adecuado, no necesitamos calcular desplazamientos y tama\u00f1os para los argumentos. Todo eso lo hace avo por nosotros.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/88085e927dd943ea0f808a28fb3ccf9b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAnteriormente, utilizamos etiquetas y goto (o saltos) para mejorar el rendimiento y enga\u00f1ar al compilador de Go, pero ahora lo hacemos desde el principio. La cuesti\u00f3n es que los bucles son un concepto de nivel m\u00e1s alto. En ensamblador, solo tenemos etiquetas y saltos.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/9a4181248eb279d89c1445a820b06649.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEl c\u00f3digo restante debe ser ya familiar y comprensible. Emulamos el bucle con etiquetas y saltos, tomamos un peque\u00f1o segmento de datos de nuestros dos slices, los combinamos mediante una operaci\u00f3n bit a bit (AND NOT en este caso) y luego colocamos el resultado en el slice resultante. Eso es todo.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/19275ead27f63092fc6596bed38a8d03.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAs\u00ed es como se ve el c\u00f3digo final en ensamblador. No necesitamos calcular desplazamientos y tama\u00f1os (resaltado en verde) ni rastrear los registros utilizados (resaltado en rojo).<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/ed68528a6f852634a5b536670c6315f0.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSi comparamos el rendimiento de la implementaci\u00f3n en ensamblador con el rendimiento de la mejor implementaci\u00f3n en Go, veremos que son iguales. Y esto es esperado. Despu\u00e9s de todo, no hicimos nada especial: simplemente reproducimos lo que har\u00eda el compilador de Go.<\/p>\n<p>Desafortunadamente, no podemos hacer que el compilador inlinet nuestras funciones escritas en ensamblador. Hasta la fecha, el compilador de Go no tiene tal capacidad, aunque existe una solicitud para agregarla desde hace bastante tiempo.<\/p>\n<p>Es por eso que es imposible obtener ventajas de las funciones peque\u00f1as en ensamblador. Debemos escribir funciones grandes, utilizar el nuevo paquete math\/bits, o evitar el ensamblador por completo.<\/p>\n<p>Ahora, veamos las versiones vectoriales de nuestras funciones.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/7f67c3cc855908fb47c7900d6e5d7f54.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPara este ejemplo, decid\u00ed aplicar AVX2, por lo que utilizaremos operaciones que trabajan con fragmentos de 32 bytes. La estructura del c\u00f3digo es muy similar a la de la versi\u00f3n escalar: carga de par\u00e1metros, solicitud de un registro general libre, etc.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/021e0424a0d733ec7db9edeb98ce1f65.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nUna de las novedades es que las operaciones vectoriales m\u00e1s amplias utilizan registros especiales m\u00e1s anchos. En el caso de fragmentos de 32 bytes, son registros con el prefijo Y. Por eso ves la funci\u00f3n YMM() en el c\u00f3digo. Si hubiera utilizado AVX-512 con fragmentos de 64 bits, el prefijo ser\u00eda Z.<\/p>\n<p>La segunda novedad es que decid\u00ed usar una optimizaci\u00f3n llamada desenrollado de bucles (loop unrolling), es decir, realizar ocho operaciones del bucle manualmente antes de saltar al inicio del bucle. Esta optimizaci\u00f3n reduce la cantidad de ramas en el c\u00f3digo y est\u00e1 limitada por la cantidad de registros libres disponibles.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/f54631d9e8c69f6f70ecace3133ae1e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n\u00bfY qu\u00e9 hay del rendimiento? \u00a1Es excelente! Logramos una aceleraci\u00f3n de aproximadamente siete veces en comparaci\u00f3n con la mejor soluci\u00f3n en Go. Impresionante, \u00bfverdad?<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/d7e85c933eecb243cfb49225b4d92c6c.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPero incluso esta implementaci\u00f3n podr\u00eda acelerarse potencialmente utilizando AVX-512, prefetching o JIT (compilador en tiempo de ejecuci\u00f3n) para el planificador de consultas. Pero ese definitivamente es un tema para otra presentaci\u00f3n.<\/p>\n<h2>Problemas de \u00edndices bitmap<\/h2>\n<p>\nAhora que hemos revisado la implementaci\u00f3n simple de un \u00edndice bitmap en Go y una mucho m\u00e1s eficiente en ensamblador, hablemos finalmente sobre por qu\u00e9 los \u00edndices bitmap se utilizan tan raramente.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/83d36a5c92ba90fd680fe8afb6cfc11f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn antiguos trabajos cient\u00edficos se mencionan tres problemas de los \u00edndices bitmap, pero trabajos m\u00e1s recientes y yo afirmamos que ya no son relevantes. No profundicemos en cada uno de estos problemas, pero los revisaremos superficialmente.<\/p>\n<h2>El problema de alta cardinalidad<\/h2>\n<p>\nEntonces, nos dicen que los \u00edndices bitmap son adecuados solo para campos de baja cardinalidad, es decir, aquellos que tienen pocos valores (por ejemplo, sexo o color de ojos), y la raz\u00f3n es que la representaci\u00f3n est\u00e1ndar de tales campos (un bit por valor) ocupar\u00eda demasiado espacio en caso de alta cardinalidad y, adem\u00e1s, estos \u00edndices bitmap estar\u00edan poco (raramente) llenos.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/0787efc4d2cb4ea4d404ca7888b33697.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/44ab4bc7c25d14fe2f53caf5d9da399d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nA veces podemos usar otra representaci\u00f3n, por ejemplo, la est\u00e1ndar que utilizamos para representar n\u00fameros. Pero fue la aparici\u00f3n de algoritmos de compresi\u00f3n lo que cambi\u00f3 todo. En las \u00faltimas d\u00e9cadas, cient\u00edficos e investigadores han ideado una gran cantidad de algoritmos de compresi\u00f3n para bitmaps. Su principal ventaja es que no es necesario descomprimir los bitmaps para realizar operaciones bit a bit; podemos llevar a cabo operaciones bit a bit directamente sobre los bitmaps comprimidos.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/ecb54fbf15aa11271bbbab01ecbda880.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nRecientemente han comenzado a aparecer enfoques h\u00edbridos, como los bitmaps roaring. Utilizan simult\u00e1neamente tres representaciones diferentes para los bitmaps: los propios bitmaps, arrays y los llamados bit runs, y equilibran entre ellos para maximizar el rendimiento y minimizar el consumo de memoria.<\/p>\n<p>Puedes encontrar bitmaps roaring en las aplicaciones m\u00e1s populares. Ya existen una gran cantidad de implementaciones para muchos lenguajes de programaci\u00f3n, incluyendo m\u00e1s de tres implementaciones para Go.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/de2adfebc431ff48c996247b453f02ae.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nOtro enfoque que puede ayudarnos a lidiar con alta cardinalidad se llama agrupaci\u00f3n (binning). Imagina que tienes un campo que representa la altura de una persona. La altura es un n\u00famero de punto flotante, pero nosotros, los humanos, no lo pensamos de esa manera. Para nosotros no hay diferencia entre una altura de 185,2 cm y 185,3 cm.<\/p>\n<p>As\u00ed que podemos agrupar valores similares en grupos dentro de 1 cm.<\/p>\n<p>Y si adem\u00e1s sabemos que hay muy pocas personas con altura menor a 50 cm y mayor a 250 cm, entonces podemos, en esencia, convertir un campo de cardinalidad infinita en un campo con aproximadamente 200 valores \u00fanicos.<\/p>\n<p>Por supuesto, si es necesario, podemos hacer una filtraci\u00f3n adicional despu\u00e9s.<\/p>\n<h2>El problema del gran ancho de banda<\/h2>\n<p>\nEl siguiente problema de los \u00edndices bitmap es que su actualizaci\u00f3n puede ser muy costosa.<\/p>\n<p>Las bases de datos deben permitir la actualizaci\u00f3n de datos en el momento en que potencialmente cientos de otras solicitudes est\u00e1n buscando esos datos. Necesitamos bloqueos para evitar problemas de acceso concurrente a los datos o cualquier otro problema de concurrencia. Y donde hay un gran bloqueo, surge un problema: contenti\u00f3n de bloqueo, cuando ese bloqueo se convierte en un cuello de botella.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/0ae1bf925542d286f8b7b245c160a35e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEste problema se puede resolver o eludir mediante sharding o utilizando \u00edndices versionados.<\/p>\n<p>El sharding es una cosa simple y bien conocida. Puedes shardear un \u00edndice bitmap de la misma manera que shardear\u00edas cualquier otro dato. En lugar de un gran bloqueo, obtienes un mont\u00f3n de bloqueos peque\u00f1os y as\u00ed evitas la contenti\u00f3n de bloqueo.<\/p>\n<p>La segunda forma de abordar el problema es utilizar \u00edndices versionados. Puedes tener una copia del \u00edndice que utilizas para buscar o leer, y otra para escribir o actualizar. Y cada cierto tiempo (por ejemplo, cada 100 ms o 500 ms) las duplicas y cambias de lugar. Por supuesto, este enfoque solo es aplicable en aquellos casos en los que tu aplicaci\u00f3n puede trabajar con un \u00edndice de b\u00fasqueda ligeramente desfasado.<\/p>\n<p>Estos dos enfoques se pueden utilizar al mismo tiempo: puedes tener un \u00edndice versionado shardizado.<\/p>\n<h2>Consultas m\u00e1s complejas<\/h2>\n<p>El \u00faltimo problema de los \u00edndices bitmap es que, como se nos dice, no son adecuados para tipos de consultas m\u00e1s complejas, como las consultas \"por rango\".<\/p>\n<p>Y es cierto, si lo piensas, las operaciones bit a bit como AND, OR, etc. no son muy adecuadas para consultas tipo \"Mu\u00e9strame hoteles con precios de habitaciones entre 200 y 300 d\u00f3lares por noche\".<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/7bc2e129cad46fb5875c2b3018c39ff7.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nUna soluci\u00f3n ingenua y muy imprudente ser\u00eda tomar los resultados para cada valor de d\u00f3lar y combinarlos mediante una operaci\u00f3n OR.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/b9f8fc7945caa1866f8e0cfa8a04bd98.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nUna soluci\u00f3n un poco m\u00e1s correcta ser\u00eda utilizar agrupamiento. Por ejemplo, en grupos de 50 d\u00f3lares. Esto acelerar\u00eda nuestro proceso 50 veces.<\/p>\n<p>Pero el problema tambi\u00e9n se resuelve f\u00e1cilmente utilizando una representaci\u00f3n creada espec\u00edficamente para este tipo de consultas. En trabajos acad\u00e9micos, se llama bitmaps codificados por rango.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/644a420628b21f220a7af1ff15c4031f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn esta representaci\u00f3n, no solo establecemos un bit para alg\u00fan valor (por ejemplo, 200), sino que establecemos ese valor y todo lo que est\u00e1 por encima. 200 y m\u00e1s. Lo mismo para 300: 300 y m\u00e1s. Y as\u00ed sucesivamente.<\/p>\n<p>Usando esta representaci\u00f3n, podemos responder a este tipo de consulta de b\u00fasqueda recorriendo el \u00edndice solo dos veces. Primero obtendremos una lista de hoteles donde el precio de la habitaci\u00f3n es inferior a 300 d\u00f3lares, y luego eliminaremos aquellos donde el costo de la habitaci\u00f3n es inferior a 199 d\u00f3lares. Listo.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/cc2bb58d7d7a51495c62ec7da81e2d12.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nTe sorprender\u00e1 saber que incluso las consultas geoespaciales son posibles utilizando \u00edndices bitmap. La clave es usar una representaci\u00f3n geogr\u00e1fica que rodee tus coordenadas con una figura geom\u00e9trica. Por ejemplo, S2 de Google. La figura debe ser representable mediante tres o m\u00e1s l\u00edneas que se crucen y que puedan ser numeradas. As\u00ed podremos convertir nuestra consulta geoespacial en varias consultas \"por intervalo\" (a lo largo de estas l\u00edneas numeradas).<\/p>\n<h2>Soluciones listas<\/h2>\n<p>\nEspero haber despertado un poco de tu inter\u00e9s y que ahora tengas otra herramienta \u00fatil en tu arsenal. Si alguna vez necesitas hacer algo similar, sabr\u00e1s en qu\u00e9 direcci\u00f3n mirar.<\/p>\n<p>Sin embargo, no todos tienen el tiempo, la paciencia y los recursos para crear \u00edndices bitmap desde cero. Especialmente los m\u00e1s avanzados, que utilizan SIMD, por ejemplo.<\/p>\n<p>Afortunadamente, hay varias soluciones listas que pueden ayudarte.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/817b47cb189a758756b602ec9cf319e1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Bitmaps roaring<\/h2>\n<p>\nPrimero, est\u00e1 la biblioteca de bitmaps roaring de la que ya he hablado. Contiene todos los contenedores y operaciones de bits necesarias para crear un \u00edndice bitmap completo.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/57f61a51485c174666b52dc2063fabe8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nDesafortunadamente, por el momento, ninguna de las implementaciones en Go utiliza SIMD, por lo que las implementaciones en Go son menos eficientes que las de C, por ejemplo.<\/p>\n<h2>Pilosa<\/h2>\n<p>\nOtro producto que puede ayudarte es la base de datos Pilosa, que b\u00e1sicamente solo tiene \u00edndices bitmap. Es una soluci\u00f3n relativamente nueva, pero est\u00e1 ganando adeptos a una velocidad incre\u00edble.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/d3870a979e1e73d093fe5d5e9bb71cd8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPilosa utiliza bitmaps roaring en su interior y te permite usarlos, simplificando y explicando todas esas cosas sobre las que he hablado anteriormente: agrupaci\u00f3n, bitmaps codificados por rango, el concepto de campo, etc.<\/p>\n<p>Echemos un vistazo r\u00e1pido a un ejemplo de uso de Pilosa para responder a una pregunta que ya conocen.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/dfee7abfd653cc19d9aa8b64c64f3e4e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEl ejemplo es muy similar a lo que han visto anteriormente. Creamos un cliente para el servidor Pilosa, creamos un \u00edndice y los campos necesarios, luego llenamos nuestros campos con datos aleatorios seg\u00fan probabilidades y, finalmente, realizamos la consulta conocida.<\/p>\n<p>Despu\u00e9s de esto, utilizamos NOT en el campo 'expensive', luego cruzamos el resultado (o AND) con el campo 'terrace' y con el campo 'reservations'. Y al final, obtenemos el resultado final.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/8d66c6d68019c2297b6c15b700f06a3a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEspero que en un futuro cercano este nuevo tipo de \u00edndices, los \u00edndices bitmap, tambi\u00e9n aparezca en bases de datos como MySQL y PostgreSQL.<br \/>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/7a8e33d576c7fb6376a173ee038b4206.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Conclusi\u00f3n<\/h2>\n<p>\n<img decoding=\"async\" alt=\"\u00cdndices bitmap en Go: b\u00fasqueda a velocidad salvaje\" src=\"\/wp-content\/uploads\/2019\/05\/c62caa9ad6f2d96056c80326f4fa9a0d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSi todav\u00eda no se han quedado dormidos, gracias. Tuve que tocar muchos temas brevemente debido al tiempo limitado, pero espero que la presentaci\u00f3n haya sido \u00fatil y, quiz\u00e1s, incluso motivadora.<\/p>\n<p>Es bueno conocer los \u00edndices bitmap, incluso si no los necesitan en este momento. Que sean una herramienta m\u00e1s en su caja.<\/p>\n<p>Hemos revisado varios trucos para aumentar el rendimiento en Go y aquellas cosas con las que el compilador de Go a\u00fan no se maneja muy bien. Esto es, sin duda, algo que todos los programadores de Go deben saber.<\/p>\n<p>Eso es todo lo que quer\u00eda contar. \u00a1Gracias!<br \/>\n<br \/>Fuente: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/badoo\/blog\/451938\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441 \u044d\u0442\u0438\u043c \u0434\u043e\u043a\u043b\u0430\u0434\u043e\u043c \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435 \u043d\u0430 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 GopherCon Russia 2019 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435 \u0438 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u2014 \u043d\u0430 \u043c\u0438\u0442\u0430\u043f\u0435 \u0432 \u041d\u0438\u0436\u043d\u0435\u043c \u041d\u043e\u0432\u0433\u043e\u0440\u043e\u0434\u0435. \u0420\u0435\u0447\u044c \u0432 \u043d\u0451\u043c \u0438\u0434\u0451\u0442 \u043e bitmap-\u0438\u043d\u0434\u0435\u043a\u0441\u0435 \u2014 \u043c\u0435\u043d\u0435\u0435 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0451\u043d\u043d\u043e\u043c, \u0447\u0435\u043c B-tree, \u043d\u043e \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u043c. \u0414\u0435\u043b\u044e\u0441\u044c \u0437\u0430\u043f\u0438\u0441\u044c\u044e \u0432\u044b\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u044f \u043d\u0430 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c \u0438 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0439 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c. \u041c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":25469,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-33793","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47Bitmap-\u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0432 Go: \u043f\u043e\u0438\u0441\u043a \u043d\u0430 \u0434\u0438\u043a\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:54:42+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:54:42+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47 \u00cdndices bitmap en Go: b\u00fasqueda a velocidades impresionantes | ProHoster","description":"Palabras de apertura que pronunci\u00e9.","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47Bitmap-\u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0432 Go: \u043f\u043e\u0438\u0441\u043a \u043d\u0430 \u0434\u0438\u043a\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 | ProHoster","og:description":"\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441.","og:url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:54:42+00:00","article:modified_time":"2019-10-31T18:54:42+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"33793","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 16:43:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 02:33:25","updated":"2026-01-21 16:43:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/33793","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=33793"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/33793\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/25469"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=33793"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=33793"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=33793"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}