Browse Source

big: Improve `int_bitfield_extract`.

Jeroen van Rijn 4 years ago
parent
commit
2fbff25a18
3 changed files with 78 additions and 29 deletions
  1. 39 9
      core/math/big/example.odin
  2. 38 19
      core/math/big/helpers.odin
  3. 1 1
      core/math/big/radix.odin

+ 39 - 9
core/math/big/example.odin

@@ -12,6 +12,8 @@ package big
 
 import "core:fmt"
 import "core:mem"
+import "core:time"
+import rnd "core:math/rand"
 
 print_configation :: proc() {
 	fmt.printf(
@@ -40,32 +42,51 @@ _SQR_TOOM_CUTOFF,
 );
 }
 
+Category :: enum {
+	itoa,
+	atoi,
+};
+Event :: struct {
+	t: time.Duration,
+	c: int,
+}
+Timings := [Category]Event{};
+
 print :: proc(name: string, a: ^Int, base := i8(10)) {
+	s := time.tick_now();
 	as, err := itoa(a, base);
+	Timings[.itoa].t += time.tick_since(s); Timings[.itoa].c += 1;
+
 	defer delete(as);
 	cb, _ := count_bits(a);
 	fmt.printf("%v (base: %v, bits used: %v): %v\n", name, base, cb, as);
 	if err != .None {
 		fmt.printf("%v (error: %v | %v)\n", name, err, a);
 	}
-	
 }
 
-@thread_local string_buffer: [1024]u8;
-
 demo :: proc() {
 	err: Error;
+	as:  string;
+
+	r := &rnd.Rand{};
+	rnd.init(r, 12345);
 
 	destination, source, quotient, remainder, numerator, denominator := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{};
 	defer destroy(destination, source, quotient, remainder, numerator, denominator);
 
-	for i in 1..=10 {
-		err = rand(destination, 1200); // 1200 random bits
+	err = rand(destination, 120, r);
+	// print("destination", destination, 10);
+	for _ in 0 ..< 10_000 {
 		if err != .None {
-			fmt.printf("rand error: %v\n", err);
+			fmt.printf("set error: %v\n", err);
 		} else {
-			fmt.printf("#%3d: ", i);
-			print("", destination);
+			s := time.tick_now();
+			as, err = itoa(destination, 16);
+			e := time.tick_since(s);
+			Timings[.itoa].t += e; Timings[.itoa].c += 1;
+			assert(as == "B38677A01B8EF75CF434CA68677495", as);
+			delete(as);
 		}
 	}
 }
@@ -75,9 +96,18 @@ main :: proc() {
 	mem.tracking_allocator_init(&ta, context.allocator);
 	context.allocator = mem.tracking_allocator(&ta);
 
-	print_configation();
+	// print_configation();
 	demo();
 
+	fmt.printf("\nTimings:\n");
+	for v, i in Timings {
+		if v.c > 0 {
+			avg   := time.duration_milliseconds(time.Duration(f64(v.t) / f64(v.c)));
+			total := time.duration_milliseconds(time.Duration(v.t));
+			fmt.printf("%v: %.3f ms (avg), %.3f (total, %v calls)\n", i, avg, total, v.c);
+		}
+	}
+
 	if len(ta.allocation_map) > 0 {
 		for _, v in ta.allocation_map {
 			fmt.printf("Leaked %v bytes @ %v\n", v.size, v.location);

+ 38 - 19
core/math/big/helpers.odin

@@ -11,7 +11,7 @@ package big
 
 import "core:mem"
 import "core:intrinsics"
-import "core:math/rand"
+import rnd "core:math/rand"
 
 /*
 	Deallocates the backing memory of one or more `Int`s.
@@ -177,7 +177,7 @@ neg :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) {
 /*
 	Helpers to extract values from the `Int`.
 */
-extract_bit :: proc(a: ^Int, bit_offset: int) -> (bit: DIGIT, err: Error) {
+int_extract_bit :: proc(a: ^Int, bit_offset: int) -> (bit: DIGIT, err: Error) {
 	/*
 		Check that `a`is usable.
 	*/
@@ -195,10 +195,7 @@ extract_bit :: proc(a: ^Int, bit_offset: int) -> (bit: DIGIT, err: Error) {
 	return 1 if ((a.digit[limb] & i) != 0) else 0, .None;
 }
 
-/*
-	TODO: Optimize.
-*/
-extract_bits :: proc(a: ^Int, offset, count: int) -> (res: _WORD, err: Error) {
+int_bitfield_extract :: proc(a: ^Int, offset, count: int) -> (res: _WORD, err: Error) {
 	/*
 		Check that `a`is usable.
 	*/
@@ -210,18 +207,40 @@ extract_bits :: proc(a: ^Int, offset, count: int) -> (res: _WORD, err: Error) {
 		return 0, .Invalid_Argument;
 	}
 
-	v: DIGIT;
-	e: Error;
-	for shift := 0; shift < count; shift += 1 {
-		o   := offset + shift;
-		v, e = extract_bit(a, o);
-		if e != .None {
-			break;
-		}
-		res = res + _WORD(v) << uint(shift);
+	limb_lo :=  offset          / _DIGIT_BITS;
+	bits_lo :=  offset          % _DIGIT_BITS;
+	limb_hi := (offset + count) / _DIGIT_BITS;
+	bits_hi := (offset + count) % _DIGIT_BITS;
+
+	if limb_lo < 0 || limb_lo >= a.used || limb_hi < 0 || limb_hi >= a.used {
+		return 0, .Invalid_Argument;
 	}
 
-	return res, e;
+	for i := limb_hi; i >= limb_lo; i -= 1 {
+		res <<= _DIGIT_BITS;
+
+		/*
+			Determine which bits to extract from each DIGIT. The whole DIGIT's worth by default.
+		*/
+		bit_count  := _DIGIT_BITS;
+		bit_offset := 0;
+		if i == limb_lo {
+			bit_count  -= bits_lo;
+			bit_offset = _DIGIT_BITS - bit_count;
+		} else if i == limb_hi {
+			bit_count  = bits_hi;
+			bit_offset = 0;
+		}
+
+		d := a.digit[i];
+
+		v := (d >> uint(bit_offset)) & DIGIT(1 << uint(bit_count - 1));
+		m := DIGIT(1 << uint(bit_count-1));
+		r := v & m;
+
+		res |= _WORD(r);
+	}
+	return res, .None;
 }
 
 /*
@@ -507,9 +526,9 @@ count_lsb :: proc(a: ^Int) -> (count: int, err: Error) {
 	return count, .None;
 }
 
-int_random_digit :: proc(r: ^rand.Rand = nil) -> (res: DIGIT) {
+int_random_digit :: proc(r: ^rnd.Rand = nil) -> (res: DIGIT) {
 	when _DIGIT_BITS == 60 { // DIGIT = u64
-		return DIGIT(rand.uint64(r)) & _MASK;
+		return DIGIT(rnd.uint64(r)) & _MASK;
 	} else when _DIGIT_BITS == 28 { // DIGIT = u32
 		return DIGIT(rand.uint32(r)) & _MASK;
 	} else {
@@ -519,7 +538,7 @@ int_random_digit :: proc(r: ^rand.Rand = nil) -> (res: DIGIT) {
 	return 0; // We shouldn't get here.
 }
 
-int_rand :: proc(dest: ^Int, bits: int, r: ^rand.Rand = nil) -> (err: Error) {
+int_rand :: proc(dest: ^Int, bits: int, r: ^rnd.Rand = nil) -> (err: Error) {
 	bits := bits;
 
 	if bits <= 0 { return .Invalid_Argument; }

+ 1 - 1
core/math/big/radix.odin

@@ -202,7 +202,7 @@ int_itoa_raw :: proc(a: ^Int, radix: i8, buffer: []u8, size := int(-1), zero_ter
 
 		for offset := 0; offset < count; offset += 4 {
 			bits_to_get := int(min(count - offset, shift));
-			if digit, err = extract_bits(a, offset, bits_to_get); err != .None {
+			if digit, err = int_bitfield_extract(a, offset, bits_to_get); err != .None {
 				return len(buffer) - available, .Invalid_Argument;
 			}
 			available -= 1;