/*! * \file sketch_support.c * * \brief Support routines for managing bitmaps used in sketches */ /*! @addtogroup grp_desc_stats * * @par About: * MADlib provides a number of descriptive statistics to complement * SQL's builtin aggregation functions (COUNT, SUM, MAX, MIN, AVG, STDDEV). * When possible we try * to provide high-peformance algorithms that run in a single (parallel) * pass of the data without overflowing main memory. In some cases this * is achieved by approximation * algorithms (e.g. sketches) -- for those algorithms it's important to * understand that answers are guaranteed mathematically to be within * plus-or-minus a small epsilon of the right answer with high probability. * It's always good to go back the research papers cited in the documents to * understand the caveats involved. * * \par * In this module you will find methods for: * - order statistics (quantiles, median) * - distinct counting * - histogramming * - frequent-value counting */ /* @addtogroup grp_sketches */ #include /* THIS CODE MAY NEED TO BE REVISITED TO ENSURE ALIGNMENT! */ #include #include #include #include #include #include #include #include #include "sketch_support.h" /*! * Simple linear function to find the rightmost bit that's set to one * (i.e. the # of trailing zeros to the right). * \param bits a bitmap containing many fm sketches * \param numsketches the number of sketches in the bits variable * \param sketchsz_bits the size of each sketch in bits * \param sketchnum the sketch number in which we want to find the rightmost one */ uint32 rightmost_one(uint8 *bits, size_t numsketches, size_t sketchsz_bits, size_t sketchnum) { (void) numsketches; /* avoid warning about unused parameter */ uint8 *s = &(((uint8 *)(bits))[sketchnum*sketchsz_bits/8]); int i; uint32 c = 0; /* output: c will count trailing zero bits, */ if (sketchsz_bits % (sizeof(uint32)*CHAR_BIT)) elog( ERROR, "number of bits per sketch is %u, must be a multiple of sizeof(uint32) = %u", (uint32)sketchsz_bits, (uint32)sizeof(uint32)); /* * loop through the chunks of bits from right to left, counting zeros. * stop when we hit a 1. * XXX currently we look at CHAR_BIT (8) bits at a time * which would seem to avoid any 32- vs. 64-bit concerns. * might be worth tuning this up to do 32 bits at a time. */ for (i = sketchsz_bits/(CHAR_BIT) -1; i >= 0; i--) { uint32 v = (uint32) (s[i]); if (!v) /* all CHAR_BIT of these bits are 0 */ c += CHAR_BIT /* * sizeof(uint32) */; else { c += ui_rightmost_one(v); break; /* we found a 1 in this value of i, so we stop looping here. */ } } return c; } /*! * Simple linear function to find the leftmost zero (# leading 1's) * Would be nice to unify with the previous -- e.g. a foomost_bar function * where foo would be left or right, and bar would be 0 or 1. * \param bits a bitmap containing many fm sketches * \param numsketches the number of sketches in the bits variable * \param the size of each sketch in bits * \param the sketch number in which we want to find the rightmost one */ uint32 leftmost_zero(uint8 *bits, size_t numsketches, size_t sketchsz_bits, size_t sketchnum) { uint8 * s = &(((uint8 *)bits)[sketchnum*sketchsz_bits/8]); unsigned i; uint32 c = 0; /* output: c will count trailing zero bits, */ uint32 maxbyte = pow(2,8) - 1; if (sketchsz_bits % (sizeof(uint32)*8)) elog( ERROR, "number of bits per sketch is %u, must be a multiple of sizeof(uint32) = %u", (uint32)sketchsz_bits, (uint32)sizeof(uint32)); if (sketchsz_bits > numsketches*8) elog(ERROR, "sketch sz declared at %u, but bitmap is only %u", (uint32)sketchsz_bits, (uint32)numsketches*8); /* * loop through the chunks of bits from left to right, counting ones. * stop when we hit a 0. */ for (i = 0; i < sketchsz_bits/(CHAR_BIT); i++) { uint32 v = (uint32) s[i]; if (v == maxbyte) c += CHAR_BIT /* * sizeof(uint32) */; else { /* * reverse and invert v, then do rightmost_one * magic reverse from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits */ uint8 b = ((s[i] * 0x0802LU & 0x22110LU) | (s[i] * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; v = (uint32) b ^ 0xff; c += ui_rightmost_one(v); break; /* we found a 1 in this value of i, so we stop looping here. */ } } return c; } /* * Given an array of n b-bit bitmaps, turn on the k'th most * significant bit of the j'th bitmap. * Both j and k are zero-indexed, BUT the bitmaps are indexed left-to-right, * whereas significant bits are (of course!) right-to-left within the bitmap. * * This function makes destructive updates; the caller should make sure to check * that we're being called in an agg context! * \param bitmap an array of FM sketches * \param numsketches # of sketches in the array * \param sketchsz_bits # of BITS per sketch * \param sketchnum index of the sketch to modify (from left, zero-indexed) * \param bitnum bit offset (from right, zero-indexed) in that sketch */ Datum array_set_bit_in_place(bytea *bitmap, int32 numsketches, int32 sketchsz_bits, int32 sketchnum, int32 bitnum) { char mask; char bytes_per_sketch = sketchsz_bits/CHAR_BIT; if (sketchnum >= numsketches || sketchnum < 0) elog(ERROR, "sketch offset exceeds the number of sketches (0-based)"); if (bitnum >= sketchsz_bits || bitnum < 0) elog( ERROR, "bit offset exceeds the number of bits per sketch (0-based)"); if (sketchsz_bits % sizeof(uint32)) elog( ERROR, "number of bits per sketch is %d, must be a multiple of sizeof(uint32) = %u", sketchsz_bits, (uint32)sizeof(uint32)); mask = 0x1 << bitnum%8; /* the bit to be modified within the proper byte (from the right) */ ((char *)(VARDATA(bitmap)))[sketchnum*bytes_per_sketch /* left boundary of the proper sketch */ + ( (bytes_per_sketch - 1) /* right boundary of the proper sketch */ - bitnum/CHAR_BIT /* the byte to be modified (from the right) */ ) ] |= mask; PG_RETURN_BYTEA_P(bitmap); } /*! * Simple linear function to find the rightmost one (# trailing zeros) in an uint32. * Based on * http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear * Look there for fancier ways to do this. * \param v an integer */ uint32 ui_rightmost_one(uint32 v) { uint32 c; v = (v ^ (v - 1)) >> 1; /* Set v's trailing 0s to 1s and zero rest */ for (c = 0; v; c++) { v >>= 1; } return c; } /*! * the postgres internal md5 routine only provides public access to text output * here we convert that text (in hex notation) back into bytes. * postgres hex output has two hex characters for each 8-bit byte. * so the output of this will be exactly half as many bytes as the input. * \param hex a string encoding bytes in hex * \param bytes out-value that will hold binary version of hex * \param hexlen the length of the hex string */ void hex_to_bytes(char *hex, uint8 *bytes, size_t hexlen) { uint32 i; for (i = 0; i < hexlen; i+=2) /* +2 to consume 2 hex characters each time */ { char c1 = hex[i]; /* high-order bits */ char c2 = hex[i+1]; /* low-order bits */ int b1 = 0, b2 = 0; if (isdigit(c1)) b1 = c1 - '0'; else if (c1 >= 'A' && c1 <= 'F') b1 = c1 - 'A' + 10; else if (c1 >= 'a' && c1 <= 'f') b1 = c1 - 'a' + 10; if (isdigit(c2)) b2 = c2 - '0'; else if (c2 >= 'A' && c2 <= 'F') b2 = c2 - 'A' + 10; else if (c2 >= 'a' && c2 <= 'f') b2 = c2 - 'a' + 10; bytes[i/2] = b1*16 + b2; /* i/2 because our for loop is incrementing by 2 */ } } /*! debugging utility to output strings in binary */ void bit_print(uint8 *c, int numbytes) { int j, i, l; char p[MD5_HASHLEN_BITS+2]; uint32 n; for (j = 0, l=0; j < numbytes; j++) { n = (uint32)c[j]; i = 1<<(CHAR_BIT - 1); while (i > 0) { if (n & i) p[l++] = '1'; else p[l++] = '0'; i >>= 1; } p[l] = '\0'; } elog(NOTICE, "bitmap: %s", p); } /*! * Run the datum through an md5 hash. No need to special-case variable-length types, * we'll just hash their length header too. * The POSTGRES code for md5 returns a bytea with a textual representation of the * md5 result. We then convert it back into binary. * \param dat a Postgres Datum * \param typOid Postgres type Oid * \returns a bytea containing the hashed bytes */ bytea *sketch_md5_bytea(Datum dat, Oid typOid) { // according to postgres' libpq/md5.c, need 33 bytes to hold // null-terminated md5 string char outbuf[MD5_HASHLEN*2+1]; bytea *out = palloc0(MD5_HASHLEN+VARHDRSZ); bool byval = get_typbyval(typOid); int len = ExtractDatumLen(dat, get_typlen(typOid), byval, -1); void *datp = DatumExtractPointer(dat, byval); /* * it's very common to be hashing 0 for countmin sketches. Rather than * hard-code it here, we cache on first lookup. In future a bigger cache here * would be nice. */ static bool zero_cached = false; static char md5_of_0_mem[MD5_HASHLEN+VARHDRSZ]; static bytea *md5_of_0 = (bytea *) &md5_of_0_mem; if (byval && len == sizeof(int64) && *(int64 *)datp == 0 && zero_cached) { return md5_of_0; } else pg_md5_hash(datp, len, outbuf); hex_to_bytes(outbuf, (uint8 *)VARDATA(out), MD5_HASHLEN*2); SET_VARSIZE(out, MD5_HASHLEN+VARHDRSZ); if (byval && len == sizeof(int64) && *(int64 *)datp == 0 && !zero_cached) { zero_cached = true; memcpy(md5_of_0, out, MD5_HASHLEN+VARHDRSZ); } return out; } /* TEST ROUTINES */ PG_FUNCTION_INFO_V1(sketch_array_set_bit_in_place); Datum sketch_array_set_bit_in_place(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(sketch_rightmost_one); Datum sketch_rightmost_one(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(sketch_leftmost_zero); Datum sketch_leftmost_zero(PG_FUNCTION_ARGS); #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) /* * Unfortunately, the VARSIZE_ANY_EXHDR macro produces a warning because of the * way type promotion and artihmetic conversions works in C99. See ยง6.1.3.8 of * the C99 standard. */ #pragma GCC diagnostic push #endif #pragma GCC diagnostic ignored "-Wsign-compare" Datum sketch_rightmost_one(PG_FUNCTION_ARGS) { bytea *bitmap = (bytea *)PG_GETARG_BYTEA_P(0); size_t sketchsz = PG_GETARG_INT32(1); /* size in bits */ size_t sketchnum = PG_GETARG_INT32(2); /* from the left! */ char * bits = VARDATA(bitmap); size_t len = (size_t)(VARSIZE_ANY_EXHDR(bitmap)); return rightmost_one((uint8 *)bits, len, sketchsz, sketchnum); } Datum sketch_leftmost_zero(PG_FUNCTION_ARGS) { bytea *bitmap = (bytea *)PG_GETARG_BYTEA_P(0); size_t sketchsz = PG_GETARG_INT32(1); /* size in bits */ size_t sketchnum = PG_GETARG_INT32(2); /* from the left! */ char * bits = VARDATA(bitmap); size_t len = (size_t)(VARSIZE_ANY_EXHDR(bitmap)); return leftmost_zero((uint8 *)bits, len, sketchsz, sketchnum); } #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) #pragma GCC diagnostic pop #endif Datum sketch_array_set_bit_in_place(PG_FUNCTION_ARGS) { bytea *bitmap = (bytea *)PG_GETARG_BYTEA_P(0); int32 numsketches = PG_GETARG_INT32(1); int32 sketchsz = PG_GETARG_INT32(2); /* size in bits */ int32 sketchnum = PG_GETARG_INT32(3); /* from the left! */ int32 bitnum = PG_GETARG_INT32(4); /* from the right! */ return array_set_bit_in_place(bitmap, numsketches, sketchsz, sketchnum, bitnum); } /* In some cases with large numbers, log2 seems to round up incorrectly. */ int32 safe_log2(int64 x) { int32 out = trunc(log2(x)); while ((((int64)1) << out) > x) out--; return out; } /* * We need process c string and var especially here, it is really ugly, * but we have to. * Because here the user can change the binary representations directly. */ size_t ExtractDatumLen(Datum x, int len, bool byVal, size_t capacity) { (void) byVal; /* avoid warning about unused parameter */ size_t size = 0; size_t idx = 0; char *data = NULL; if (len > 0) { size = len; if (capacity != (size_t)-1 && size > capacity) { elog(ERROR, "invalid transition state"); } } else if (len == -1) { if (capacity == (size_t)-1) { size = VARSIZE_ANY(DatumGetPointer(x)); } else { data = (char*)DatumGetPointer(x); if ((capacity >= VARHDRSZ) || (capacity >= 1 && VARATT_IS_1B(data))) { size = VARSIZE_ANY(data); } else { elog(ERROR, "invalid transition state"); } } } else if (len == -2) { if (capacity == (size_t)-1) { return strlen((char *)DatumGetPointer(x)) + 1; } else { data = (char*)DatumGetPointer(x); size = 0; for (idx = 0; idx < capacity && data[idx] != 0; idx ++, size ++) { } if (idx >= capacity) { elog(ERROR, "invalid transition state"); } size ++; } } else { elog(ERROR, "Datum typelength error in ExtractDatumLen: len is %u", (unsigned)len); return 0; } return size; } /* * walk an array of int64s and convert word order of int64s to big-endian * if force == true, convert even if this arch is big-endian */ void int64_big_endianize(uint64 *bytes64, uint32 numbytes, bool force) { uint32 i; uint32 *bytes32 = (uint32 *)bytes64; uint32 x; uint32 total = 0; #ifdef WORDS_BIGENDIAN bool small_endian = false; #else bool small_endian = true; #endif if (numbytes % 8) elog(ERROR, "illegal numbytes argument to big_endianize: not a multiple of 8"); if (small_endian || force) { for (i = 0; i < numbytes/8; i+=2) { x = bytes32[i]; bytes32[i] = bytes32[i+1]; bytes32[i+1] = x; total++; } } }