Sieve of Atkin(SoA)の翻訳されたPythonソースからのAaron Mugatroydによる最後の回答はそれほど悪くはありませんが、次のようにいくつかの重要な最適化を見逃しているため、いくつかの点で改善できます。
彼の答えは、完全なモジュロ60の元のAtkinおよびBernsteinバージョンのSieveを使用していませんが、Wikipediaの記事からの疑似コードのわずかに改善されたバリエーションを使用しているため、トグル/カリング操作を組み合わせた数値のふるい範囲の約0.36倍を使用します。以下の私のコードは、アトキンのふるいに関する私のコメントのように、適度に効率的な非ページセグメントの擬似コードを使用しています。 7分の2。
彼のコードは奇数の表現のみを持つことでバッファサイズを削減しますが、私のコードはさらにビットパックを使用して、3と5で割り切れる数、および「奇数のみ」で示される2で割り切れる数の表現を排除します。これにより、メモリ要件がさらにほぼ半分(8/15)に削減され、平均メモリアクセス時間が短縮されるため、CPUキャッシュをより有効に活用して速度をさらに向上させることができます。
私のコードは、高速ルックアップテーブル(LUT)ポップカウント手法を使用して素数をカウントし、彼が使用するビットごとの手法を使用する約1秒と比較して、カウントにほとんど時間がかかりません。ただし、このサンプルコードでは、コードの時間指定部分からわずかな時間が取られています。
最後に、私のコードは、内部ループごとに最小限のコードでビット操作操作を最適化します。たとえば、奇数の表現インデックスを生成するために継続的な右シフトを使用せず、実際には、すべての内部ループを一定のモジュロ(ビット位置に等しい)演算として書き込むことにより、少しだけシフトします。同様に、アーロンの変換されたコードは、操作において非常に非効率的です。たとえば、プライムスクエアフリーカリングでは、プライムの2乗をインデックスに追加してから、チェックを必要としないように2倍を追加するのではなく、奇数の結果をチェックします。 ; 次に、すべてのループの場合と同様に、内側のループでカリング操作を実行する前に、数値を1ずつ右にシフト(2で除算)することで、チェックも冗長にします。この非効率的なコードは 1回の操作あたりの時間のほとんどがRAMメモリアクセスで使用されるため、この「大きなふるいバッファアレイ」手法を使用すると、広い範囲の実行時間に大きな違いが生じます(10億の範囲で約37 CPUクロックサイクル以上)。 、ただし、CPUキャッシュに収まるより小さな範囲の場合よりも実行時間が大幅に遅くなります。つまり、操作ごとの実行速度の下限が高すぎます。
コードは次のとおりです。
//Sieve of Atkin based on full non page segmented modulo 60 implementation...
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace NonPagedSoA {
//implements the non-paged Sieve of Atkin (full modulo 60 version)...
class SoA : IEnumerable<ulong> {
private ushort[] buf = null;
private long cnt = 0;
private long opcnt = 0;
private static byte[] modPRMS = { 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59, 61 };
private static ushort[] modLUT;
private static byte[] cntLUT;
//initialize the private LUT's...
static SoA() {
modLUT = new ushort[60];
for (int i = 0, m = 0; i < modLUT.Length; ++i) {
if ((i & 1) != 0 || (i + 7) % 3 == 0 || (i + 7) % 5 == 0) modLUT[i] = 0;
else modLUT[i] = (ushort)(1 << (m++));
}
cntLUT = new byte[65536];
for (int i = 0; i < cntLUT.Length; ++i) {
var c = 0;
for (int j = i; j > 0; j >>= 1) c += j & 1;
cntLUT[i] = (byte)c;
}
}
//initialization and all the work producing the prime bit array done in the constructor...
public SoA(ulong range) {
this.opcnt = 0;
if (range < 7) {
if (range > 1) {
cnt = 1;
if (range > 2) this.cnt += (long)(range - 1) / 2;
}
this.buf = new ushort[0];
}
else {
this.cnt = 3;
var nrng = range - 7; var lmtw = nrng / 60;
//initialize sufficient wheels to non-prime
this.buf = new ushort[lmtw + 1];
//Put in candidate primes:
//for the 4 * x ^ 2 + y ^ 2 quadratic solution toggles - all x odd y...
ulong n = 6; // equivalent to 13 - 7 = 6...
for (uint x = 1, y = 3; n <= nrng; n += (x << 3) + 4, ++x, y = 1) {
var cb = n; if (x <= 1) n -= 8; //cancel the effect of skipping the first one...
for (uint i = 0; i < 15 && cb <= range; cb += (y << 2) + 4, y += 2, ++i) {
var cbd = cb / 60; var cm = modLUT[cb % 60];
if (cm != 0)
for (uint c = (uint)cbd, my = y + 15; c < buf.Length; c += my, my += 30) {
buf[c] ^= cm; // ++this.opcnt;
}
}
}
//for the 3 * x ^ 2 + y ^ 2 quadratic solution toggles - x odd y even...
n = 0; // equivalent to 7 - 7 = 0...
for (uint x = 1, y = 2; n <= nrng; n += ((x + x + x) << 2) + 12, x += 2, y = 2) {
var cb = n;
for (var i = 0; i < 15 && cb <= range; cb += (y << 2) + 4, y += 2, ++i) {
var cbd = cb / 60; var cm = modLUT[cb % 60];
if (cm != 0)
for (uint c = (uint)cbd, my = y + 15; c < buf.Length; c += my, my += 30) {
buf[c] ^= cm; // ++this.opcnt;
}
}
}
//for the 3 * x ^ 2 - y ^ 2 quadratic solution toggles all x and opposite y = x - 1...
n = 4; // equivalent to 11 - 7 = 4...
for (uint x = 2, y = x - 1; n <= nrng; n += (ulong)(x << 2) + 4, y = x, ++x) {
var cb = n; int i = 0;
for ( ; y > 1 && i < 15 && cb <= nrng; cb += (ulong)(y << 2) - 4, y -= 2, ++i) {
var cbd = cb / 60; var cm = modLUT[cb % 60];
if (cm != 0) {
uint c = (uint)cbd, my = y;
for ( ; my >= 30 && c < buf.Length; c += my - 15, my -= 30) {
buf[c] ^= cm; // ++this.opcnt;
}
if (my > 0 && c < buf.Length) { buf[c] ^= cm; /* ++this.opcnt; */ }
}
}
if (y == 1 && i < 15) {
var cbd = cb / 60; var cm = modLUT[cb % 60];
if ((cm & 0x4822) != 0 && cbd < (ulong)buf.Length) { buf[cbd] ^= cm; /* ++this.opcnt; */ }
}
}
//Eliminate squares of base primes, only for those on the wheel:
for (uint i = 0, w = 0, pd = 0, pn = 0, msk = 1; w < this.buf.Length ; ++i) {
uint p = pd + modPRMS[pn];
ulong sqr = (ulong)p * (ulong)p; //to handle ranges above UInt32.MaxValue
if (sqr > range) break;
if ((this.buf[w] & msk) != 0) { //found base prime, square free it...
ulong s = sqr - 7;
for (int j = 0; s <= nrng && j < modPRMS.Length; s = sqr * modPRMS[j] - 7, ++j) {
var cd = s / 60; var cm = (ushort)(modLUT[s % 60] ^ 0xFFFF);
//may need ulong loop index for ranges larger than two billion
//but buf length only good to about 2^31 * 60 = 120 million anyway,
//even with large array setting and half that with 32-bit...
for (ulong c = cd; c < (ulong)this.buf.Length; c += sqr) {
this.buf[c] &= cm; // ++this.opcnt;
}
}
}
if (msk >= 0x8000) { msk = 1; pn = 0; ++w; pd += 60; }
else { msk <<= 1; ++pn; }
}
//clear any overflow primes in the excess space in the last wheel/word:
var ndx = nrng % 60; //clear any primes beyond the range
for (; modLUT[ndx] == 0; --ndx) ;
this.buf[lmtw] &= (ushort)((modLUT[ndx] << 1) - 1);
}
}
//uses a fast pop count Look Up Table to return the total number of primes...
public long Count {
get {
long cnt = this.cnt;
for (int i = 0; i < this.buf.Length; ++i) cnt += cntLUT[this.buf[i]];
return cnt;
}
}
//returns the number of toggle/cull operations used to sieve the prime bit array...
public long Ops {
get {
return this.opcnt;
}
}
//generate the enumeration of primes...
public IEnumerator<ulong> GetEnumerator() {
yield return 2; yield return 3; yield return 5;
ulong pd = 0;
for (uint i = 0, w = 0, pn = 0, msk = 1; w < this.buf.Length; ++i) {
if ((this.buf[w] & msk) != 0) //found a prime bit...
yield return pd + modPRMS[pn]; //add it to the list
if (msk >= 0x8000) { msk = 1; pn = 0; ++w; pd += 60; }
else { msk <<= 1; ++pn; }
}
}
//required for the above enumeration...
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
class Program {
static void Main(string[] args) {
Console.WriteLine("This program calculates primes by a simple full version of the Sieve of Atkin.\r\n");
const ulong n = 1000000000;
var elpsd = -DateTime.Now.Ticks;
var gen = new SoA(n);
elpsd += DateTime.Now.Ticks;
Console.WriteLine("{0} primes found to {1} using {2} operations in {3} milliseconds.", gen.Count, n, gen.Ops, elpsd / 10000);
//Output prime list for testing...
//Console.WriteLine();
//foreach (var p in gen) {
// Console.Write(p + " ");
//}
//Console.WriteLine();
//Test options showing what one can do with the enumeration, although more slowly...
// Console.WriteLine("\r\nThere are {0} primes with the last one {1} and the sum {2}.",gen.Count(),gen.Last(),gen.Sum(x => (long)x));
Console.Write("\r\nPress any key to exit:");
Console.ReadKey(true);
Console.WriteLine();
}
}
}
このコードは、Aaronのコードの約2倍の速度で実行されます(i7-2700K(3.5 GHz)で64ビットまたは32ビットモードを使用して約2.7秒、バッファーは約16.5メガバイト、トグル/プライムスクエアフリーカリング操作の組み合わせは約25.8億) (「++ this.opcnt」ステートメントのコメントを外すことで表示できます)10億のふるい範囲で、カウント時間なしのコードの5.4 / 6.2秒(32ビット/ 64ビット)とほぼ比較最大10億をふるいにかけるために、約3億5900万のトグル/カリング操作を組み合わせて使用すると、メモリ使用量が2倍になります。
エラトステネスのふるい(SoE)の彼の最も最適化されたナイーブオッズのみの実装よりも高速ですが、それはエラトステネスのふるいよりもアトキンのふるいを速くすることはありません。上記のSoEplusへのSoA実装では、最大のホイール因数分解が使用され、SoEはこれとほぼ同じ速度になります。
分析: 完全に最適化されたSoEの操作数は、10億のふるい範囲でのSoAの操作数とほぼ同じですが、これらの非ページ実装の主なボトルネックは、ふるいバッファーサイズが超過した場合のメモリアクセスです。 CPUキャッシュサイズ(i7の場合、1クロックサイクルアクセスで32キロバイトのL1キャッシュ、約4クロックサイクルのアクセス時間で256キロバイトのL2キャッシュ、約20クロックサイクルのアクセス時間で8メガバイトのL3キャッシュ)。 100クロックサイクル。
アルゴリズムをページセグメンテーションに適合させると、どちらもメモリアクセス速度が約8倍向上するため、使用可能なメモリに収まらない範囲をふるいにかけることができます。ただし、数百倍に急速に拡大するカリングスキャンの大きな進歩により、アルゴリズムの「プライムスクエアフリー」部分の実装が困難なため、ふるい範囲が非常に大きくなり始めると、SoEはSoAを超えて増加し続けます。ページバッファのサイズ。同様に、そしておそらくもっと深刻なことに、各ページバッファの最も低い表現での「y」の値に関して「x」の各値の新しい開始点を計算することは、非常にメモリおよび/または計算集約的になります。範囲が拡大するにつれて、ページングされたSoAの効率がSoEと比較して大幅に低下します。
EDIT_ADD: Aaron Murgatroydが使用するオッズのみのSoEは、10億のふるい範囲で約10億2600万のカリング操作を使用するため、SoAの約4倍の操作で実行されるため、実行速度は約4倍遅くなりますが、SoAは実装されていてもここでは、より複雑な内部ループがあり、特にオッズの割合がはるかに高いため、カリングスキャンのストライドはSoAのナイーブオッズよりもはるかに短いSoEのみのSoEの平均メモリアクセス時間ははるかに優れていますふるいバッファがCPUキャッシュサイズを大幅に超えているにもかかわらず(キャッシュの関連付けをより適切に使用)。これは、理論的には作業の4分の1しか実行していないように見えるにもかかわらず、上記のSoAがオッズのみのSoEの約2倍の速度である理由を説明しています。
上記のSoAと同様のモジュロ内部ループを使用するアルゴリズムを使用し、同じ2/3/5ホイール因数分解を実装した場合、SoEはカリング操作の数を約4億5000万回に減らし、約50%多くなります。演算はSoAよりも動作し、理論的にはSoAよりもわずかに遅くなりますが、この「ナイーブな」大容量メモリバッファの使用では、平均してSoAよりもカリングストライドが少し小さいため、ほぼ同じ速度で実行される可能性があります。ホイールの因数分解を2/3/5/7ホイールに増やすと、SoEカリング操作が10億のカリング範囲で約0.314に減少し、そのバージョンのSoEがこのアルゴリズムでほぼ同じ速度で実行される可能性があります。
2/3/5/7/11/13/17/19の素因数のふるい配列を事前にカリング(パターンでコピー)することにより、実行時間のコストをほとんどかけずにホイール因数分解をさらに使用して、ふるいの範囲が10億の場合、カル操作の総数は約25.1億になり、SoEは、この最適化されたバージョンのSoAでも、これらの大容量メモリバッファーのバージョンでも、SoEにまだ多くの容量がある場合でも、より高速またはほぼ同じ速度で実行されます。上記よりもコードの複雑さが少なくなります。
したがって、SoEの操作数は、ナイーブまたはオッズのみ、または2/3/5のホイール因数分解バージョンから大幅に削減できるため、操作数はSoAの場合とほぼ同じであることがわかります。同時に、より複雑でない内部ループとより効率的なメモリアクセスの両方により、操作あたりの時間は実際には短くなる可能性があります。 END_EDIT_ADD
EDIT_ADD2: ここで、上記のSoAと同様の定数モジュロ/ビット位置手法を使用して、SoEのコードを追加します。これは、上記のリンク先の疑似コードに従って行われます。高いホイール因数分解と事前カリングが適用されているにもかかわらず、コードは上記のSoAよりもかなり複雑ではないため、実際には、選別操作までのSoAのトグル/カリング操作の合計数よりも少なくなります。約20億の。次のようなコード:
EDIT_FINAL以下の修正されたコードとそれに関連するコメントEND_EDIT_FINAL
//Sieve of Eratosthenes based on maximum wheel factorization and pre-culling implementation...
using System;
using System.Collections;
using System.Collections.Generic;
namespace NonPagedSoE {
//implements the non-paged Sieve of Eratosthenes (full modulo 210 version with preculling)...
class SoE : IEnumerable<ulong> {
private ushort[] buf = null;
private long cnt = 0;
private long opcnt = 0;
private static byte[] basePRMS = { 2, 3, 5, 7, 11, 13, 17, 19 };
private static byte[] modPRMS = { 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, //positions + 23
97, 101, 103, 107, 109, 113, 121, 127, 131, 137, 139, 143, 149, 151, 157, 163,
167, 169, 173, 179, 181 ,187 ,191 ,193, 197, 199, 209, 211, 221, 223, 227, 229 };
private static byte[] gapsPRMS = { 6, 2, 6, 4, 2, 4, 6, 6, 2, 6, 4, 2, 6, 4, 6, 8,
4, 2, 4, 2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6, 4,
2, 4, 6, 2, 6, 4, 2, 4, 2, 10, 2, 10, 2, 4, 2, 4 };
private static ulong[] modLUT;
private static byte[] cntLUT;
//initialize the private LUT's...
static SoE() {
modLUT = new ulong[210];
for (int i = 0, m = 0; i < modLUT.Length; ++i) {
if ((i & 1) != 0 || (i + 23) % 3 == 0 || (i + 23) % 5 == 0 || (i + 23) % 7 == 0) modLUT[i] = 0;
else modLUT[i] = 1UL << (m++);
}
cntLUT = new byte[65536];
for (int i = 0; i < cntLUT.Length; ++i) {
var c = 0;
for (int j = i ^ 0xFFFF; j > 0; j >>= 1) c += j & 1; //reverse logic; 0 is prime; 1 is composite
cntLUT[i] = (byte)c;
}
}
//initialization and all the work producing the prime bit array done in the constructor...
public SoE(ulong range) {
this.opcnt = 0;
if (range < 23) {
if (range > 1) {
for (int i = 0; i < modPRMS.Length; ++i) if (modPRMS[i] <= range) this.cnt++; else break;
}
this.buf = new ushort[0];
}
else {
this.cnt = 8;
var nrng = range - 23; var lmtw = nrng / 210; var lmtwt3 = lmtw * 3;
//initialize sufficient wheels to prime
this.buf = new ushort[lmtwt3 + 3]; //initial state of all zero's is all potential prime.
//initialize array to account for preculling the primes of 11, 13, 17, and 19;
//(2, 3, 5, and 7 already eliminated by the bit packing to residues).
for (int pn = modPRMS.Length - 4; pn < modPRMS.Length; ++pn) {
uint p = modPRMS[pn] - 210u; ulong pt3 = p * 3;
ulong s = p * p - 23;
ulong xrng = Math.Min(9699709, nrng); // only do for the repeating master pattern size
ulong nwrds = (ulong)Math.Min(138567, this.buf.Length);
for (int j = 0; s <= xrng && j < modPRMS.Length; s += p * gapsPRMS[(pn + j++) % 48]) {
var sm = modLUT[s % 210];
var si = (sm < (1UL << 16)) ? 0UL : ((sm < (1UL << 32)) ? 1UL : 2UL);
var cd = s / 210 * 3 + si; var cm = (ushort)(sm >> (int)(si << 4));
for (ulong c = cd; c < nwrds; c += pt3) { //tight culling loop for size of master pattern
this.buf[c] |= cm; // ++this.opcnt; //reverse logic; mark composites with ones.
}
}
}
//Now copy the master pattern so it repeats across the main buffer, allow for overflow...
for (long i = 138567; i < this.buf.Length; i += 138567)
if (i + 138567 <= this.buf.Length)
Array.Copy(this.buf, 0, this.buf, i, 138567);
else Array.Copy(this.buf, 0, this.buf, i, this.buf.Length - i);
//Eliminate all composites which are factors of base primes, only for those on the wheel:
for (uint i = 0, w = 0, wi = 0, pd = 0, pn = 0, msk = 1; w < this.buf.Length; ++i) {
uint p = pd + modPRMS[pn];
ulong sqr = (ulong)p * (ulong)p;
if (sqr > range) break;
if ((this.buf[w] & msk) == 0) { //found base prime, mark its composites...
ulong s = sqr - 23; ulong pt3 = p * 3;
for (int j = 0; s <= nrng && j < modPRMS.Length; s += p * gapsPRMS[(pn + j++) % 48]) {
var sm = modLUT[s % 210];
var si = (sm < (1UL << 16)) ? 0UL : ((sm < (1UL << 32)) ? 1UL : 2UL);
var cd = s / 210 * 3 + si; var cm = (ushort)(sm >> (int)(si << 4));
for (ulong c = cd; c < (ulong)this.buf.Length; c += pt3) { //tight culling loop
this.buf[c] |= cm; // ++this.opcnt; //reverse logic; mark composites with ones.
}
}
}
++pn;
if (msk >= 0x8000) { msk = 1; ++w; ++wi; if (wi == 3) { wi = 0; pn = 0; pd += 210; } }
else msk <<= 1;
}
//clear any overflow primes in the excess space in the last wheel/word:
var ndx = nrng % 210; //clear any primes beyond the range
for (; modLUT[ndx] == 0; --ndx) ;
var cmsk = (~(modLUT[ndx] - 1)) << 1; //force all bits above to be composite ones.
this.buf[lmtwt3++] |= (ushort)cmsk;
this.buf[lmtwt3++] |= (ushort)(cmsk >> 16);
this.buf[lmtwt3] |= (ushort)(cmsk >> 32);
}
}
//uses a fast pop count Look Up Table to return the total number of primes...
public long Count {
get {
long cnt = this.cnt;
for (int i = 0; i < this.buf.Length; ++i) cnt += cntLUT[this.buf[i]];
return cnt;
}
}
//returns the number of cull operations used to sieve the prime bit array...
public long Ops {
get {
return this.opcnt;
}
}
//generate the enumeration of primes...
public IEnumerator<ulong> GetEnumerator() {
yield return 2; yield return 3; yield return 5; yield return 7;
yield return 11; yield return 13; yield return 17; yield return 19;
ulong pd = 0;
for (uint i = 0, w = 0, wi = 0, pn = 0, msk = 1; w < this.buf.Length; ++i) {
if ((this.buf[w] & msk) == 0) //found a prime bit...
yield return pd + modPRMS[pn];
++pn;
if (msk >= 0x8000) { msk = 1; ++w; ++wi; if (wi == 3) { wi = 0; pn = 0; pd += 210; } }
else msk <<= 1;
}
}
//required for the above enumeration...
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
class Program {
static void Main(string[] args) {
Console.WriteLine("This program calculates primes by a simple maximually wheel factorized version of the Sieve of Eratosthenes.\r\n");
const ulong n = 1000000000;
var elpsd = -DateTime.Now.Ticks;
var gen = new SoE(n);
elpsd += DateTime.Now.Ticks;
Console.WriteLine("{0} primes found to {1} using {2} operations in {3} milliseconds.", gen.Count, n, gen.Ops, elpsd / 10000);
// Console.WriteLine();
// foreach (var p in gen) {
// Console.Write(p + " ");
// }
// Console.WriteLine();
// Console.WriteLine("\r\nThere are {0} primes with the last one {1} and the sum {2}.",gen.Count(),gen.Last(),gen.Sum(x => (long)x));
Console.Write("\r\nPress any key to exit:");
Console.ReadKey(true);
Console.WriteLine();
}
}
}
このコードは、実際には上記のSoAよりも数パーセント高速に実行されます。これは、操作がわずかに少なく、10億の範囲のこの大きなアレイサイズの主なボトルネックは、40〜100CPUクロックサイクルのようなメモリアクセス時間です。 CPUとメモリの仕様によって異なります。これは、ほとんどの時間がメモリアクセスの待機に費やされるため、コードの最適化(操作の総数を減らす以外)は効果がないことを意味します。いずれにせよ、巨大なメモリバッファを使用することは、広い範囲をふるいにかけるための最も効率的な方法ではありません。同じ最大ホイール因数分解を使用したページセグメンテーションを使用したSoEの場合、最大で約8倍の改善が見られます。マルチプロセッシング)。
SoAの漸近的な複雑さの軽減による利益は、関連するページ処理のオーバーヘッド要因によって急速に消費されるため、SoAは、SoEと比較して40億をはるかに超える範囲で実際に不足しているのは、ページのセグメンテーションとマルチプロセッシングの実装です。プライムスクエアフリー処理とはるかに多くのページ開始アドレスの計算。あるいは、メモリ消費の莫大なコストとこれらのマーカーストア構造へのアクセスのさらなる非効率性でマーカーをRAMメモリに格納することによってこれを克服します。 END_EDIT_ADD2
要するに、SoAは、完全にホイールファクタリングされたSoEと比較して、実際には実用的なふるいではありません。漸近的な複雑さが増し、完全に最適化されたSoEにパフォーマンスが近づき始めるのと同じように、相対的なメモリアクセス時間とページセグメンテーションの複雑さ、および一般的にはより複雑で記述が難しいことに関する実際の実装の詳細。私の意見では、SoEと比較すると、実用的なふるいというよりも、興味深い知的概念と精神的運動です。
いつの日か、これらの手法をマルチスレッドページにセグメント化されたエラトステネスのふるいに適応させて、C#でAtkinとBernsteinによる「C」でのSoAの「primegen」実装とほぼ同じ速度にし、広範囲にわたって水から吹き飛ばします。シングルスレッドでも約40億を超え、i7でマルチスレッドを使用すると最大約4の速度がさらに向上します(ハイパースレッディングを含む8つのコア)。