Skip to content

Commit 125a4d3

Browse files
authored
JavaScript/solution_1: improve unflagging of prime candidates (PlummersSoftwareLLC#862)
1 parent 9bf98fb commit 125a4d3

File tree

8 files changed

+673
-521
lines changed

8 files changed

+673
-521
lines changed

Diff for: PrimeJavaScript/solution_1/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ COPY run.sh \
3838
ENTRYPOINT [ "./run.sh" ]
3939

4040
# For manual debugging
41-
# ENTRYPOINT [ "/bin/bash" ]
41+
#ENTRYPOINT [ "/bin/bash" ]

Diff for: PrimeJavaScript/solution_1/PrimeJavaScript.js

+123-107
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,30 @@ Date: 2022-07-10
1717
*/
1818

1919
"use strict";
20+
const NOW_UNITS_PER_SECOND = 1000;
21+
const WORD_SIZE = 32;
2022

23+
let config = {
24+
sieveSize: 1000000,
25+
timeLimitSeconds: 5,
26+
verbose: false,
27+
runtime: ''
28+
};
2129

22-
let runtime = "";
23-
let verbose = false;
2430
try
2531
{
2632
!!Deno;
27-
runtime = "deno";
28-
verbose = Deno.args.includes("verbose");
33+
config.runtime = "deno";
34+
config.verbose = Deno.args.includes("verbose");
2935
}
3036
catch
3137
{
38+
const { performance } = require('perf_hooks');
3239
const runtimeParts = process.argv[0].split("/");
33-
runtime = runtimeParts[runtimeParts.length - 1];
34-
verbose = process.argv.includes("verbose");
40+
config.runtime = runtimeParts[runtimeParts.length - 1];
41+
config.verbose = process.argv.includes("verbose");
3542
}
3643

37-
38-
const NOW_UNITS_PER_SECOND = 1000;
39-
40-
41-
// Historical data for validating our results - the number of primes
42-
// to be found under some limit, such as 168 primes under 1000
43-
const knownPrimeCounts = {
44-
10: 4,
45-
100: 25,
46-
1000: 168,
47-
10000: 1229,
48-
100000: 9592,
49-
1000000: 78498,
50-
10000000: 664579,
51-
100000000: 5761455
52-
};
53-
5444
// 32-bit bitarray for javascript, with only needed functions
5545
// int32, not uint and not 64bit because: javascript uses 32int
5646
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND
@@ -69,13 +59,63 @@ class BitArray
6959
this.wordArray[wordOffset] |= (1 << bitOffset);
7060
}
7161

62+
setBitsTrue(range_start, step, range_stop) {
63+
if (step > WORD_SIZE/2) {
64+
// steps are large: check if the range is large enough to reuse the same mask
65+
let range_stop_unique = range_start + 32 * step;
66+
if (range_stop_unique > range_stop) {
67+
// range is not large enough for repetition (32 * step)
68+
for (let index = range_start; index < range_stop; index += step) {
69+
this.setBitTrue(index);
70+
}
71+
return;
72+
}
73+
// range is large enough to reuse the mask
74+
const range_stop_word = range_stop >>> 5;
75+
for (let index = range_start; index < range_stop_unique; index += step) {
76+
let wordOffset = index >>> 5;
77+
const bitOffset = index & 31;
78+
const mask = (1 << bitOffset);
79+
do {
80+
this.wordArray[wordOffset] |= mask;
81+
wordOffset += step; // pattern repeats on word level after {step} words
82+
} while (wordOffset <= range_stop_word);
83+
}
84+
return;
85+
}
86+
87+
// optimized for small sizes: set wordvalue multiple times before committing to memory
88+
let index = range_start;
89+
let wordOffset = index >>> 5; // 1 word = 2ˆ5 = 32 bit, so shift 5, much faster than /32
90+
let wordValue = this.wordArray[wordOffset];
91+
92+
while (index < range_stop) {
93+
const bitOffset = index & 31; // use & (and) for remainder, faster than modulus of /32
94+
wordValue |= (1 << bitOffset);
95+
96+
index += step;
97+
const newwordOffset = index >>> 5; // 1 word = 2ˆ5 = 32 bit, so shift 5, much faster than /32
98+
if (newwordOffset != wordOffset) { // moving to new word: store value and get new value
99+
this.wordArray[wordOffset] = wordValue;
100+
wordOffset = newwordOffset;
101+
wordValue = this.wordArray[wordOffset];
102+
}
103+
}
104+
this.wordArray[wordOffset] = wordValue; // make sure last value is stored
105+
}
106+
72107
testBitTrue(index)
73108
{
74109
const wordOffset = index >>> 5;
75110
const bitOffset = index & 31;
76111
return this.wordArray[wordOffset] & (1 << bitOffset); // use a mask to only get the bit at position bitOffset. >0=true, 0=false
77112
}
78113

114+
searchBitFalse(index)
115+
{
116+
while (this.testBitTrue(index)) { index++ }; // will stop automatically because bits were 0 filled
117+
return index;
118+
}
79119
}
80120

81121
/*
@@ -89,35 +129,32 @@ class PrimeSieve
89129
constructor(sieveSize)
90130
{
91131
this.sieveSize = sieveSize;
92-
this.oddsize = sieveSize >>> 1;
93-
this.bitarray = new BitArray(1 + this.oddsize);
132+
this.sieveSizeInBits = sieveSize >>> 1;
133+
this.bitArray = new BitArray(1 + this.sieveSizeInBits);
94134
}
95135

96136
runSieve()
97137
{
98-
const q = Math.ceil(Math.sqrt(this.oddsize)); // convert to integer with ceil
138+
const q = Math.ceil(Math.sqrt(this.sieveSizeInBits)); // convert to integer with ceil
139+
let factor = 1;
99140

100-
for (let factor = 1; factor <= q; factor++)
141+
while (factor < q)
101142
{
102-
if (!this.bitarray.testBitTrue(factor))
103-
{
104-
const step = factor * 2 + 1;
105-
const start = factor * factor * 2 + factor + factor;
143+
const step = factor * 2 + 1;
144+
const start = factor * factor * 2 + factor + factor;
106145

107-
for (let multiple = start; multiple < this.oddsize; multiple = multiple + step)
108-
{
109-
this.bitarray.setBitTrue(multiple); // mark every multiple of this prime
110-
}
111-
}
146+
this.bitArray.setBitsTrue(start, step, this.sieveSizeInBits); // mark every multiple of this prime
147+
factor = this.bitArray.searchBitFalse(factor + 1);
112148
}
149+
return this;
113150
}
114151

115152
countPrimes()
116153
{
117154
let total = 1; // account for prime 2
118-
for (let index = 1; index < this.oddsize; index++)
155+
for (let index = 1; index < this.sieveSizeInBits; index++)
119156
{
120-
if (!this.bitarray.testBitTrue(index)) // if bit is false, it's a prime, because non-primes are marked true
157+
if (!this.bitArray.testBitTrue(index)) // if bit is false, it's a prime, because non-primes are marked true
121158
{
122159
total++;
123160
}
@@ -128,14 +165,54 @@ class PrimeSieve
128165
getPrimes(max = 100)
129166
{
130167
const primes = [2]; // 2 is a special prime
131-
for (let factor = 1, count = 0; factor < this.oddsize; factor++)
168+
for (let factor = 1, count = 0; factor < this.sieveSizeInBits; factor++)
132169
{
133170
if (count >= max) break;
134-
if (!this.bitarray.testBitTrue(factor)) count = primes.push(factor * 2 + 1);
171+
if (!this.bitArray.testBitTrue(factor)) count = primes.push(factor * 2 + 1);
135172
}
136173
return primes;
137174
}
138175

176+
validatePrimeCount(verbose)
177+
{
178+
// Historical data for validating our results - the number of primes
179+
// to be found under some limit, such as 168 primes under 1000
180+
const maxShowPrimes = 100;
181+
const knownPrimeCounts = {
182+
10: 4,
183+
100: 25,
184+
1000: 168,
185+
10000: 1229,
186+
100000: 9592,
187+
1000000: 78498,
188+
10000000: 664579,
189+
100000000: 5761455
190+
};
191+
const countedPrimes = this.countPrimes();
192+
const primeArray = this.getPrimes(maxShowPrimes);
193+
194+
let validResult = false;
195+
if (this.sieveSize in knownPrimeCounts)
196+
{
197+
const knownPrimeCount = knownPrimeCounts[this.sieveSize];
198+
validResult = (knownPrimeCount == countedPrimes);
199+
if (!validResult)
200+
console.log(
201+
"\nError: invalid result.",
202+
`Limit for ${this.sieveSize} should be ${knownPrimeCount} `,
203+
`but result contains ${countedPrimes} primes`
204+
);
205+
}
206+
else console.log(
207+
`Warning: cannot validate result of ${countedPrimes} primes:`,
208+
`limit ${this.sieveSize} is not in the known list of number of primes!`
209+
);
210+
211+
if (verbose)
212+
console.log(`\nThe first ${maxShowPrimes} found primes are:`, primeArray);
213+
214+
return validResult;
215+
}
139216
}
140217

141218
// run the sieve for timeLimitSeconds
@@ -157,80 +234,19 @@ const runSieveBatch = (sieveSize, timeLimitSeconds = 5) =>
157234
return nrOfPasses;
158235
}
159236

160-
// get a single sieve (for validation and statistics)
161-
const evalSieve = (sieveSize, maxShowPrimes = 100) =>
237+
// main procedure
238+
const main = ({ sieveSize, timeLimitSeconds, verbose, runtime }) =>
162239
{
163-
const sieve = new PrimeSieve(sieveSize);
164-
sieve.runSieve();
165-
return {
166-
countedPrimes: sieve.countPrimes(),
167-
primeArray: sieve.getPrimes(maxShowPrimes)
168-
}
169-
}
240+
// validate algorithm - run one time
241+
const validResult = new PrimeSieve(sieveSize).runSieve().validatePrimeCount(verbose);
242+
if (!validResult) return false;
170243

171-
/*
172-
main procedure
173-
*/
174-
175-
const main = ({ sieveSize, timeLimitSeconds, verbose, maxShowPrimes }) =>
176-
{
177-
// run once, without threads
178244
//measure time running the batch
179245
const timeStart = performance.now();
180246
const totalPasses = runSieveBatch(sieveSize, timeLimitSeconds);
181247
const timeEnd = performance.now();
182248
const durationInSec = (timeEnd - timeStart) / NOW_UNITS_PER_SECOND;
183-
184-
// validate algorithm - run one final time on the result
185-
const sieveResult = evalSieve(sieveSize);
186-
let validResult = false;
187-
if (sieveSize in knownPrimeCounts)
188-
{
189-
const knownPrimeCount = knownPrimeCounts[sieveSize];
190-
validResult = (knownPrimeCount == sieveResult.countedPrimes);
191-
if (!validResult)
192-
console.log(
193-
"\nError: invalid result.",
194-
`Limit for ${sieveSize} should be ${knownPrimeCount} `,
195-
`but result contains ${sieveResult.countedPrimes} primes`
196-
);
197-
}
198-
else console.log(
199-
`Warning: cannot validate result of ${sieveResult.countedPrimes} primes:`,
200-
`limit ${sieveSize} is not in the known list of number of primes!`
201-
);
202-
203-
if (validResult)
204-
{
205-
const res = [
206-
`\nrogiervandam-${runtime}`,
207-
totalPasses.toString(),
208-
durationInSec.toString(),
209-
"1",
210-
"algorithm=base,faithful=yes,bits=1"
211-
];
212-
console.log(res.join(";"));
213-
}
214-
215-
if (verbose)
216-
{
217-
console.log(`\nThe first ${maxShowPrimes} found primes are:`, sieveResult.primeArray);
218-
console.log(
219-
`Passes: ${totalPasses},`,
220-
`Time: ${(durationInSec).toFixed(2)},`,
221-
`Avg: ${(durationInSec / totalPasses).toFixed(8)} (sec/pass),`,
222-
`Sieve size: ${sieveSize},`,
223-
`Primes: ${sieveResult.countedPrimes},`,
224-
`Valid: ${validResult}`
225-
);
226-
}
249+
console.log(`\nrogiervandam-${runtime};${totalPasses};${durationInSec};1;algorithm=base,faithful=yes,bits=1`);
227250
}
228251

229-
const config = {
230-
sieveSize: 1000000,
231-
timeLimitSeconds: 5,
232-
verbose: verbose,
233-
maxShowPrimes: verbose ? 100 : 0
234-
};
235-
236252
main(config);

0 commit comments

Comments
 (0)