This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
haas:summer2017:cprog:projects:pnc2 [2017/07/07 23:28] – wedge | haas:summer2017:cprog:projects:pnc2 [2017/07/08 12:47] (current) – [Approximating array size] wedge | ||
---|---|---|---|
Line 235: | Line 235: | ||
And now we can proceed with the rest of our prime implementation. | And now we can proceed with the rest of our prime implementation. | ||
+ | |||
+ | =====Allocating large arrays===== | ||
+ | Being new to arrays, you may not know it yet, but there is an upper bound to how big you can declare an array at **compile time**. | ||
+ | |||
+ | That is, using the conventional array declaration: | ||
+ | |||
+ | <code c> | ||
+ | data_type arrayname[size]; | ||
+ | </ | ||
+ | |||
+ | It is likely dependent on some combination of the execution environment (32-, 64-bit?) and the compiler, but in practice I've found things generally get unhappy if we try to request array sizes at some point beyond 1-4million elements (which makes sense, most processes in modern operating systems allocate a default stack size of 4-8MiB... if a short int is 2 bytes, how many consecutive short ints before you hit 4MiB?) | ||
+ | |||
+ | As we'll be needing array sizes considerably larger than this for the project (will we? If I could drop a hint: **yes, we will**), how do we get around this? | ||
+ | |||
+ | The answer: **runtime memory allocation** | ||
+ | |||
+ | We can allocate memory at run-time, which gives us the benefit of populating variables and performing equations to derive more specific values. This is also a rather powerful programming technique/ | ||
+ | |||
+ | To do so, we ignore the fake facade that is the array, and treat it like it actually exists to the computer: **a pointer**. | ||
+ | |||
+ | So when you declare your array, instead of using the square bracket notation, do it as a pointer instead: | ||
+ | |||
+ | <code c> | ||
+ | datatype *array_name | ||
+ | </ | ||
+ | |||
+ | THEN, later on in your program when it comes time to allocate the array, we use a memory allocation function, of which for our current purposes one of the following 2 could suit our needs: | ||
+ | |||
+ | * **malloc(size)** | ||
+ | * allocates **size** bytes and returns the address | ||
+ | * **calloc(count, | ||
+ | * allocates count * size bytes and returns the address; it also will scrub the memory, making sure everything is set to zero (**malloc()** does not do this; **malloc()** is also faster because of this, something to keep in mind to differentiate the two). | ||
+ | |||
+ | So, if we wanted a 32768 element integer array, to show examples allocating with each function (you'd obviously only use one, whichever best fit your usage scenario): | ||
+ | |||
+ | <code c> | ||
+ | int *array_name | ||
+ | |||
+ | // using malloc() | ||
+ | array_name = (int *) malloc (sizeof(int) * 32768); | ||
+ | |||
+ | // using calloc() | ||
+ | array_name = (int *) calloc (32768, sizeof(int)); | ||
+ | </ | ||
+ | |||
+ | What else is going on here? What's with the parenthesis and the integer pointer? | ||
+ | |||
+ | That's a typecast, and is a way of telling the compiler to contort data IN THAT INSTANCE to a certain type (because it is a different type, other than what we want). | ||
+ | |||
+ | As it turns out, **malloc()** and **calloc()** both return //raw memory//, which in C is manifested as type **< | ||
+ | |||
+ | With this, we can get around the compile time array limits, because by allocating memory at runtime, we're getting additional memory given to us, vs. trying to fit within the default initial allotment. | ||
+ | |||
+ | You should then be able to access it using regular array notation, or my preference: pointer arithmetic. | ||
+ | |||
+ | <code c> | ||
+ | // C array syntax for accessing 3rd element of array: | ||
+ | array_name[2] | ||
+ | |||
+ | // C pointer arithmetic syntax for accessing 3rd element of array: | ||
+ | *(array_name+2) | ||
+ | </ | ||
+ | |||
+ | =====Approximating array size===== | ||
+ | The nature of utilizing an array for the sieves, as you will likely discover quickly, is that the sheer quantity of elements needed increments drastically as the desired quantity goes up. | ||
+ | |||
+ | For example, if we were seeking the first 32 primes: | ||
+ | |||
+ | <cli> | ||
+ | lab46: | ||
+ | 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 | ||
+ | </ | ||
+ | |||
+ | ... we would see that the 32nd prime is 131. For the Sieve of Eratosthenes to successfully work in this case, we'd need an array size of at least 132 (if we do the standard mapping of array element to actual number, where element 0 would correspond with the number 0, and element 131 would correspond with number 131; 0-131 = 132 elements). | ||
+ | |||
+ | But that number isn't immediately known (indeed, the nature of the distribution of prime numbers is the subject of considerable mathematical exploration, | ||
+ | |||
+ | And without this knowledge of the largest prime in the desired quantity, our sieve implementation (with the requirements as I have specified) is sort of at a standstill. | ||
+ | |||
+ | So how do we get around this? I propose a combination of accepting knowledge from a more aware source (me), and a little algebraic ingenuity. Let's hack ourselves an array size equation! (Do I hear a "heck yeah!" ? "HECK YEAH!" | ||
+ | |||
+ | First, you can actually produce a lot of this data, from your **pnc1** programs (technically **pnc0** as well, but you'll be waiting a whole heck of a lot longer). | ||
+ | |||
+ | Let's take one of the purportedly best-running programs from **pnc1** (**primebrksrtoptodd**) and contort it into giving us the information that we need. | ||
+ | |||
+ | With a little bit of UNIX shell magic (again, I'm not expecting you to know this, so I'm just giving it to you, but from the abstract/ | ||
+ | |||
+ | <code bash 1> | ||
+ | # clear the screen | ||
+ | clear | ||
+ | |||
+ | # loop for a powers of 2 progression 32-4194304 (inclusive) | ||
+ | for((qty=32; | ||
+ | |||
+ | # display the quantity lead-in information | ||
+ | printf "[%7s] %7sth prime: " " | ||
+ | | ||
+ | # obtain the last/ | ||
+ | value=$(./ | ||
+ | |||
+ | # display it, along with calculating its approximate multiplier | ||
+ | printf "%8s (x%s)\n" | ||
+ | done | ||
+ | </ | ||
+ | |||
+ | We start with **qty** of 32, as that is the default starting point of **primerun** (so why not; makes sense to work with what we've got). But there' | ||
+ | |||
+ | I'm going to drop a hint: you'll very likely not be going beyond a quantity of **4194304** (at least with a primerun cut-off limit of 2 seconds), so use THAT as your upper bound of quantity, and that is why I use that number in the shell script given above. | ||
+ | |||
+ | You don't have to worry about running said shell script, as the output is right here: | ||
+ | |||
+ | <cli> | ||
+ | [ | ||
+ | [ | ||
+ | [ 128] 128th prime: | ||
+ | [ 256] 256th prime: | ||
+ | [ 512] 512th prime: | ||
+ | [ | ||
+ | [ | ||
+ | [ | ||
+ | [ | ||
+ | [ 16384] | ||
+ | [ 32768] | ||
+ | [ 65536] | ||
+ | [ 131072] | ||
+ | [ 262144] | ||
+ | [ 524288] | ||
+ | [1048576] 1048576th prime: 16290047 (x16) | ||
+ | [2097152] 2097152th prime: 34136029 (x17) | ||
+ | [4194304] 4194304th prime: 71378569 (x18) | ||
+ | </ | ||
+ | |||
+ | What we have is the following: | ||
+ | |||
+ | * first column is the specified quantity we are going to (as instantiated by primerun; I just have us doing a simple powers of 2 progression) | ||
+ | * the next column (the N-th prime), is the last prime in that quantity sequence (131 IS the 32nd prime, starting from 2) | ||
+ | * the last column, the xNUMBER, is the approximate multiplier we get by dividing the N-th prime by the quantity (and taking the ceiling (I just added one, since we're doing integer division and it'll truncate anyway), since it likely will NOT be a clean division) | ||
+ | |||
+ | So from the data given above, we can see that a rough approximation of space needed for 32 primes would be < | ||
+ | |||
+ | For 1024 we have a multiplier of 8 (< | ||
+ | |||
+ | Okay, so we've got some raw numbers, we know the array size needed for the smallest quantity (< | ||
+ | |||
+ | As you can see, there is no a clean progression at play here, so no simple patterns we can play off of. I offer a few suggestions (and for the purposes of this project, any are acceptable): | ||
+ | |||
+ | * just go with the max size (x18): **< | ||
+ | * deploy some **if()** statements and do a stepped progression. | ||
+ | |||
+ | For example, we see that since our max multiplier is 18, what if we did some factoring of 18? Say: 6, 12, 18? | ||
+ | |||
+ | We could rig up something like this: | ||
+ | |||
+ | <code c> | ||
+ | if (qty <= 128) | ||
+ | array_size = qty * 6; | ||
+ | else if (qty <= 32768) | ||
+ | array_size = qty * 12; | ||
+ | else | ||
+ | array_size = qty * 18; | ||
+ | </ | ||
+ | |||
+ | In general, though, this is just a mild annoyance in our quest to implement the sieve of Eratosthenes, | ||
+ | |||
+ | Make sense? Hopefully. And moreso, if you've been able to follow this, it is also indicative of your general level of comprehension: | ||
+ | |||
+ | And please, ASK QUESTIONS if something doesn' | ||
=====Timing===== | =====Timing===== | ||
Line 302: | Line 469: | ||
The execution of the programs is short and simple- grab the parameters, do the processing, produce the output, and then terminate. | The execution of the programs is short and simple- grab the parameters, do the processing, produce the output, and then terminate. | ||
+ | Based on specifications, | ||
+ | |||
+ | <cli> | ||
+ | lab46: | ||
+ | 5 7 11 13 17 | ||
+ | 0.000080 | ||
+ | lab46: | ||
+ | </ | ||
+ | |||
+ | And (shift the lower bound, but still count out 32 primes): | ||
+ | |||
+ | <cli> | ||
+ | lab46: | ||
+ | 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 | ||
+ | 0.000087 | ||
+ | lab46: | ||
+ | </ | ||
+ | |||
+ | And of course the same for the 3 variants, and the same error message reporting if invalid values are given. | ||
====Performance changes==== | ====Performance changes==== | ||
You may notice a change with the sieves as compared to the other algorithms you've implemented with respect to performance- there will likely be a lower bound of performance, | You may notice a change with the sieves as compared to the other algorithms you've implemented with respect to performance- there will likely be a lower bound of performance, | ||
Line 353: | Line 539: | ||
< | < | ||
- | 78: | + | 78: |
*: | *: | ||
*: | *: |