[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
質問
への最速の方法を探していました。
popcount
大きな配列のデータ というものに出会いました。
非常に奇妙
の効果があります。ループ変数を
unsigned
から
uint64_t
で、私のPCではパフォーマンスが50%低下しました。
ベンチマーク
#include <iostream>
#include <chrono>
#include <x86intrin.h>
int main(int argc, char* argv[]) {
using namespace std;
if (argc != 2) {
cerr << "usage: array_size in MB" << endl;
return -1;
}
uint64_t size = atol(argv[1])<<20;
uint64_t* buffer = new uint64_t[size/8];
char* charbuffer = reinterpret_cast<char*>(buffer);
for (unsigned i=0; i<size; ++i)
charbuffer[i] = rand()%256;
uint64_t count,duration;
chrono::time_point<chrono::system_clock> startP,endP;
{
startP = chrono::system_clock::now();
count = 0;
for( unsigned k = 0; k < 10000; k++){
// Tight unrolled loop with unsigned
for (unsigned i=0; i<size/8; i+=4) {
count += _mm_popcnt_u64(buffer[i]);
count += _mm_popcnt_u64(buffer[i+1]);
count += _mm_popcnt_u64(buffer[i+2]);
count += _mm_popcnt_u64(buffer[i+3]);
}
}
endP = chrono::system_clock::now();
duration = chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
cout << "unsigned\t" << count << '\t' << (duration/1.0E9) << " sec \t"
<< (10000.0*size)/(duration) << " GB/s" << endl;
}
{
startP = chrono::system_clock::now();
count=0;
for( unsigned k = 0; k < 10000; k++){
// Tight unrolled loop with uint64_t
for (uint64_t i=0;i<size/8;i+=4) {
count += _mm_popcnt_u64(buffer[i]);
count += _mm_popcnt_u64(buffer[i+1]);
count += _mm_popcnt_u64(buffer[i+2]);
count += _mm_popcnt_u64(buffer[i+3]);
}
}
endP = chrono::system_clock::now();
duration = chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
cout << "uint64_t\t" << count << '\t' << (duration/1.0E9) << " sec \t"
<< (10000.0*size)/(duration) << " GB/s" << endl;
}
free(charbuffer);
}
ご覧のように、ランダムなデータのバッファを作成し、そのサイズは
x
メガバイトで、ここで
x
はコマンドラインから読み込まれます。その後、バッファを反復処理し、展開されたx86の
popcount
を内在させ、popcountを実行する。より正確な結果を得るために、popcount を 10,000 回実行します。ポップカウントの時間を計測しています。上の例では、内部ループの変数が
unsigned
小文字の場合は、ループ内変数が
uint64_t
. これなら違いはないはずと思ったのですが、逆でした。
(絶対におかしい)結果
こんな感じでコンパイルしています(g++バージョン:Ubuntu 4.8.2-19ubuntu1)。
g++ -O3 -march=native -std=c++11 test.cpp -o test
以下は、私の
ハスウェル
Core i7-4770K
CPU @ 3.50GHz、動作中
test 1
(だから1MBのランダムデータ)。
- 無記号 41959360000 0.401554 秒 26.113 GB/s
- uint64_t 41959360000 0.759822秒 13.8003 GB/秒
ご覧の通り、スループットは
uint64_t
バージョンは
半分だけ
の方は
unsigned
バージョンと同じです。問題は、異なるアセンブリが生成されることにあるようですが、なぜでしょうか?まず、コンパイラのバグを思い浮かべたので、試しに
clang++
(Ubuntuの場合
Clang
バージョン3.4-1ubuntu3)。
clang++ -O3 -march=native -std=c++11 teest.cpp -o test
結果
test 1
- 無記号 41959360000 0.398293 秒 26.3267 GB/秒
- uint64_t 41959360000 0.680954秒 15.3986 GB/秒
ということで、ほぼ同じ結果で、やはりおかしいですね。
しかし、今度は超おかしいことになる。
入力から読み込まれたバッファサイズを定数に置き換えてみると
1
というように、変更します。
uint64_t size = atol(argv[1]) << 20;
になります。
uint64_t size = 1 << 20;
このように、コンパイラはコンパイル時にバッファの大きさを知ることができるようになりました。これで、最適化ができるかもしれませんね。以下は
g++
:
- 無記号 41959360000 0.509156 秒 20.5944 GB/秒
- uint64_t 41959360000 0.508673秒 20.6139 GB/秒
さて、どちらのバージョンも同じように高速です。しかし
unsigned
さらに遅くなった
! から低下した。
26
から
20 GB/s
このように、定数でないものを定数で置き換えると
非最適化
. マジで何が起こっているのかさっぱりわからない! しかし、今度は
clang++
を、新しいバージョンで。
- 無記号 41959360000 0.677009 秒 15.4884 GB/秒
- uint64_t 41959360000 0.676909秒 15.4906 GB/s
待って、何? さて、どちらのバージョンも 遅い 15GB/sという数値が出ました。このように、定数でないものを定数に置き換えることで、遅いコードでも ともに Clangの場合
を持つ同僚に聞いたところ アイビー・ブリッジ CPUは私のベンチマークをコンパイルしてください。彼は同じような結果を得たので、Haswell ではないようです。ここでは2つのコンパイラがおかしな結果を出しているので、コンパイラのバグでもなさそうです。こちらにはAMDのCPUがないので、Intelでしかテストできませんでした。
もっと狂ってください
最初の例を見てみましょう。
atol(argv[1])
を追加し
static
を変数の前に置く、つまり
static uint64_t size=atol(argv[1])<<20;
以下はg++での結果です。
- 無記号 41959360000 0.396728 秒 26.4306 GB/秒
- uint64_t 41959360000 0.509484 秒 20.5811 GB/s
イェーイ、また別の選択肢
. での26GB/sの高速は健在です。
u32
が、なんとか
u64
少なくとも13GB/s版から20GB/s版への移行は可能です
同僚のPCでは
u64
よりもさらに高速になりました。
u32
のバージョンで、最速の結果を得ることができました。
悲しいことに、これは
g++
,
clang++
は気にしないようです。
static
.
私の質問
この結果について説明してください。特に
-
との間に、どうしてこのような違いがあるのでしょうか?
u32
とu64
? - 定数でないバッファサイズを定数に置き換えることで、どのようにトリガーされるのでしょうか? 最適でないコード ?
-
の挿入はどのようにすればよいのでしょうか?
static
キーワードはu64
ループが速くなった?私の同僚のコンピューターにあるオリジナルのコードよりもさらに高速です
最適化は厄介な領域であることは承知していますが、しかし、こんな小さな変化が 100%の違い また、バッファサイズを一定にするなどの小さな要因で、結果が全く異なるものになることもあります。もちろん、26GB/sのポップカウントができるバージョンは常に持っていたいです。私が思いつく唯一の確実な方法は、この場合のアセンブリをコピーペーストして、インラインアセンブリを使用することです。小さな変更で発狂しそうなコンパイラを排除するには、これしかない。どうでしょうか?最も性能の良いコードを確実に得るための他の方法はないのでしょうか?
ディスアセンブル
以下は、さまざまな結果を得るための分解です。
から26GB/s版 g++ / u32 / 非恒等式 bufsize :
0x400af8:
lea 0x1(%rdx),%eax
popcnt (%rbx,%rax,8),%r9
lea 0x2(%rdx),%edi
popcnt (%rbx,%rcx,8),%rax
lea 0x3(%rdx),%esi
add %r9,%rax
popcnt (%rbx,%rdi,8),%rcx
add $0x4,%edx
add %rcx,%rax
popcnt (%rbx,%rsi,8),%rcx
add %rcx,%rax
mov %edx,%ecx
add %rax,%r14
cmp %rbp,%rcx
jb 0x400af8
から13GB/s版 g++ / u64 / 非恒久変数 bufsize :
0x400c00:
popcnt 0x8(%rbx,%rdx,8),%rcx
popcnt (%rbx,%rdx,8),%rax
add %rcx,%rax
popcnt 0x10(%rbx,%rdx,8),%rcx
add %rcx,%rax
popcnt 0x18(%rbx,%rdx,8),%rcx
add $0x4,%rdx
add %rcx,%rax
add %rax,%r12
cmp %rbp,%rdx
jb 0x400c00
から15GB/s版 clang++ / u64 / 非恒久変数 bufsize :
0x400e50:
popcnt (%r15,%rcx,8),%rdx
add %rbx,%rdx
popcnt 0x8(%r15,%rcx,8),%rsi
add %rdx,%rsi
popcnt 0x10(%r15,%rcx,8),%rdx
add %rsi,%rdx
popcnt 0x18(%r15,%rcx,8),%rbx
add %rdx,%rbx
add $0x4,%rcx
cmp %rbp,%rcx
jb 0x400e50
から20GB/s版 g++ / u32&u64 / const bufsize :
0x400a68:
popcnt (%rbx,%rdx,1),%rax
popcnt 0x8(%rbx,%rdx,1),%rcx
add %rax,%rcx
popcnt 0x10(%rbx,%rdx,1),%rax
add %rax,%rcx
popcnt 0x18(%rbx,%rdx,1),%rsi
add $0x20,%rdx
add %rsi,%rcx
add %rcx,%rbp
cmp $0x100000,%rdx
jne 0x400a68
から15GB/s版 clang++ / u32&u64 / const bufsize :
0x400dd0:
popcnt (%r14,%rcx,8),%rdx
add %rbx,%rdx
popcnt 0x8(%r14,%rcx,8),%rsi
add %rdx,%rsi
popcnt 0x10(%r14,%rcx,8),%rdx
add %rsi,%rdx
popcnt 0x18(%r14,%rcx,8),%rbx
add %rdx,%rbx
add $0x4,%rcx
cmp $0x20000,%rcx
jb 0x400dd0
興味深いことに、最速(26GB/s)のバージョンは最長でもあるのです! を使用する唯一のソリューションのようです。
lea
. いくつかのバージョンでは
jb
を使ってジャンプする場合と
jne
. しかし、それを除けば、どのバージョンも同等であるように思われます。100%の性能差がどこから来るのかわかりませんが、私はアセンブリを解読するのがあまり得意ではありません。最も遅い(13GB/s)バージョンは、非常に短くて良いようにさえ見えます。どなたか説明していただけませんか?
教訓
この質問に対する答えがどうであれ、私は本当に熱いループの中で学んだことがあります。
あらゆる
細部が重要になることがあります。
ホットコードに関係ないような細かいことでも
. ループ変数にどのような型を使用するかは考えたことがありませんでしたが、ご覧のようにこのような些細な変更で
100%
の違いです。バッファの格納タイプでさえも大きな違いを生むことがあります。
static
キーワードを size 変数の前に置くことです。今後、システムのパフォーマンスにとって重要な、本当にタイトでホットなループを書くときは、必ず様々なコンパイラで様々な選択肢をテストするつもりです。
また、面白いのは、すでに4回ループをアンロールしているにもかかわらず、これだけの性能差があることです。つまり、アンロールしてもなお、大きな性能差に見舞われることがあるのです。なかなか興味深いですね。
解決方法は?
原因 誤ったデータ依存性 (そして、コンパイラはそのことに気づいていない)
Sandy/Ivy Bridge および Haswell プロセッサでは、この命令。
popcnt src, dest
は、デスティネーションレジスタに誤った依存性を持っているように見えます。
dest
. この命令は書き込むだけであるにもかかわらず、この命令は
dest
は実行する前に準備ができています。 この誤った依存関係は、(現在では)Intelがerratumとして文書化している
HSD146 (Haswell(ハスウェル))
と
SKL029 (Skylake)
Skylake では、次のように修正されました。
lzcnt
と
tzcnt
.
Cannon Lake(とIce Lake)では、これを修正して
popcnt
.
bsf
/
bsr
は真の出力依存性を持っています:input=0に対して変更されない出力。
を利用する方法はありません。
- AMDだけがドキュメントを作成し、コンパイラはそれを公開していません)。
(そう、これらの命令はすべて 同じ実行単位で ).
この依存関係は、単に4つを保持するだけでなく
popcnt
は、1回のループの繰り返しで発生します。ループの繰り返しをまたぐと、プロセッサが異なるループの繰り返しを並列化することは不可能になります。
その
unsigned
vs.
uint64_t
などの微調整は、直接問題には影響しません。しかし、それらは変数にレジスタを割り当てるレジスタアロケータに影響を与えます。
あなたの場合、速度は、レジスタ・アロケータが何を決めたかによって、(誤った)依存関係の連鎖に貼り付けられたものが直接の原因となります。
-
13GB/sはチェーンがある。
popcnt
-add
-popcnt
-popcnt
→ 次の繰り返し -
15GB/sはチェーンがあります。
popcnt
-add
-popcnt
-add
→ 次の繰り返し -
20GB/sはチェーンがあります。
popcnt
-popcnt
→ 次の繰り返し -
26GB/sはチェーンがあります。
popcnt
-popcnt
→ 次の繰り返し
20GB/sと26GB/sの差は、間接アドレッシングによるちょっとしたアーティファクトと思われます。いずれにせよ、この速度に達すると、プロセッサは他のボトルネックにぶつかり始めます。
これをテストするために、インラインアセンブリを使ってコンパイラをバイパスし、まさに私が望むアセンブリを得ることができました。また
count
ベンチマークを混乱させるかもしれない他の依存関係をすべて断ち切るためです。
以下はその結果です。
Sandy Bridge Xeon @ 3.5 GHz。 (テストコードの詳細は下部にあります)
-
GCC 4.6.3です。
g++ popcnt.cpp -std=c++0x -O3 -save-temps -march=native
- Ubuntu 12
レジスタが違う 18.6195 GB/s
.L4:
movq (%rbx,%rax,8), %r8
movq 8(%rbx,%rax,8), %r9
movq 16(%rbx,%rax,8), %r10
movq 24(%rbx,%rax,8), %r11
addq $4, %rax
popcnt %r8, %r8
add %r8, %rdx
popcnt %r9, %r9
add %r9, %rcx
popcnt %r10, %r10
add %r10, %rdi
popcnt %r11, %r11
add %r11, %rsi
cmpq $131072, %rax
jne .L4
同じレジスター 8.49272 GB/秒
.L9:
movq (%rbx,%rdx,8), %r9
movq 8(%rbx,%rdx,8), %r10
movq 16(%rbx,%rdx,8), %r11
movq 24(%rbx,%rdx,8), %rbp
addq $4, %rdx
# This time reuse "rax" for all the popcnts.
popcnt %r9, %rax
add %rax, %rcx
popcnt %r10, %rax
add %rax, %rsi
popcnt %r11, %rax
add %rax, %r8
popcnt %rbp, %rax
add %rax, %rdi
cmpq $131072, %rdx
jne .L9
同じレジスターでチェーンが切れている。 17.8869 GB/s
.L14:
movq (%rbx,%rdx,8), %r9
movq 8(%rbx,%rdx,8), %r10
movq 16(%rbx,%rdx,8), %r11
movq 24(%rbx,%rdx,8), %rbp
addq $4, %rdx
# Reuse "rax" for all the popcnts.
xor %rax, %rax # Break the cross-iteration dependency by zeroing "rax".
popcnt %r9, %rax
add %rax, %rcx
popcnt %r10, %rax
add %rax, %rsi
popcnt %r11, %rax
add %rax, %r8
popcnt %rbp, %rax
add %rax, %rdi
cmpq $131072, %rdx
jne .L14
では、コンパイラの何が悪かったのでしょうか?
GCCもVisual Studioも、以下のことを認識していないようです。
popcnt
には、このような誤った依存関係があります。とはいえ、このような誤った依存関係は珍しいことではありません。ただ、コンパイラがそれに気づいているかどうかが問題なのです。
popcnt
は、必ずしも最もよく使われる命令ではありません。ですから、主要なコンパイラがこのようなことを見落としても、それほど驚くことではありません。また、この問題に言及した文書もどこにもないようです。インテルが公表しないのであれば、誰かが偶然に遭遇するまで、外部の誰も知らないことになる。
( 更新しました。 バージョン4.9.2現在 GCC はこの誤った依存性を認識しており、最適化を有効にした場合、それを補うコードを生成します。Clang、MSVC、そして Intel 自身の ICC を含む他のベンダーの主要なコンパイラーは、まだこのマイクロアーキテクチャの誤りを認識しておらず、これを補償するコードは生成されません)。
なぜCPUはこのような誤った依存関係を持つのでしょうか?
と同じ実行ユニットで動作していると推測されます。
bsf
/
bsr
どの
する
は出力依存性を持っています。 (
POPCNTはハードウェアでどのように実装されていますか?
). これらの命令について、Intel は input=0 の整数結果を "undefined" (ZF=1) と文書化していますが、Intel のハードウェアは古いソフトウェアを壊さないように、実際にはより強い保証をしています: 変更されない出力です。 AMDはこの挙動を文書化しています。
おそらく、この実行ユニットの一部のuopsを出力に依存させ、他のuopsを依存させないようにするのは、何らかの不都合があったのだと思われます。
AMDプロセッサには、このような誤った依存関係はないようです。
参考までに、テストコードの全文を以下に掲載します。
#include <iostream>
#include <chrono>
#include <x86intrin.h>
int main(int argc, char* argv[]) {
using namespace std;
uint64_t size=1<<20;
uint64_t* buffer = new uint64_t[size/8];
char* charbuffer=reinterpret_cast<char*>(buffer);
for (unsigned i=0;i<size;++i) charbuffer[i]=rand()%256;
uint64_t count,duration;
chrono::time_point<chrono::system_clock> startP,endP;
{
uint64_t c0 = 0;
uint64_t c1 = 0;
uint64_t c2 = 0;
uint64_t c3 = 0;
startP = chrono::system_clock::now();
for( unsigned k = 0; k < 10000; k++){
for (uint64_t i=0;i<size/8;i+=4) {
uint64_t r0 = buffer[i + 0];
uint64_t r1 = buffer[i + 1];
uint64_t r2 = buffer[i + 2];
uint64_t r3 = buffer[i + 3];
__asm__(
"popcnt %4, %4 \n\t"
"add %4, %0 \n\t"
"popcnt %5, %5 \n\t"
"add %5, %1 \n\t"
"popcnt %6, %6 \n\t"
"add %6, %2 \n\t"
"popcnt %7, %7 \n\t"
"add %7, %3 \n\t"
: "+r" (c0), "+r" (c1), "+r" (c2), "+r" (c3)
: "r" (r0), "r" (r1), "r" (r2), "r" (r3)
);
}
}
count = c0 + c1 + c2 + c3;
endP = chrono::system_clock::now();
duration=chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
cout << "No Chain\t" << count << '\t' << (duration/1.0E9) << " sec \t"
<< (10000.0*size)/(duration) << " GB/s" << endl;
}
{
uint64_t c0 = 0;
uint64_t c1 = 0;
uint64_t c2 = 0;
uint64_t c3 = 0;
startP = chrono::system_clock::now();
for( unsigned k = 0; k < 10000; k++){
for (uint64_t i=0;i<size/8;i+=4) {
uint64_t r0 = buffer[i + 0];
uint64_t r1 = buffer[i + 1];
uint64_t r2 = buffer[i + 2];
uint64_t r3 = buffer[i + 3];
__asm__(
"popcnt %4, %%rax \n\t"
"add %%rax, %0 \n\t"
"popcnt %5, %%rax \n\t"
"add %%rax, %1 \n\t"
"popcnt %6, %%rax \n\t"
"add %%rax, %2 \n\t"
"popcnt %7, %%rax \n\t"
"add %%rax, %3 \n\t"
: "+r" (c0), "+r" (c1), "+r" (c2), "+r" (c3)
: "r" (r0), "r" (r1), "r" (r2), "r" (r3)
: "rax"
);
}
}
count = c0 + c1 + c2 + c3;
endP = chrono::system_clock::now();
duration=chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
cout << "Chain 4 \t" << count << '\t' << (duration/1.0E9) << " sec \t"
<< (10000.0*size)/(duration) << " GB/s" << endl;
}
{
uint64_t c0 = 0;
uint64_t c1 = 0;
uint64_t c2 = 0;
uint64_t c3 = 0;
startP = chrono::system_clock::now();
for( unsigned k = 0; k < 10000; k++){
for (uint64_t i=0;i<size/8;i+=4) {
uint64_t r0 = buffer[i + 0];
uint64_t r1 = buffer[i + 1];
uint64_t r2 = buffer[i + 2];
uint64_t r3 = buffer[i + 3];
__asm__(
"xor %%rax, %%rax \n\t" // <--- Break the chain.
"popcnt %4, %%rax \n\t"
"add %%rax, %0 \n\t"
"popcnt %5, %%rax \n\t"
"add %%rax, %1 \n\t"
"popcnt %6, %%rax \n\t"
"add %%rax, %2 \n\t"
"popcnt %7, %%rax \n\t"
"add %%rax, %3 \n\t"
: "+r" (c0), "+r" (c1), "+r" (c2), "+r" (c3)
: "r" (r0), "r" (r1), "r" (r2), "r" (r3)
: "rax"
);
}
}
count = c0 + c1 + c2 + c3;
endP = chrono::system_clock::now();
duration=chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
cout << "Broken Chain\t" << count << '\t' << (duration/1.0E9) << " sec \t"
<< (10000.0*size)/(duration) << " GB/s" << endl;
}
free(charbuffer);
}
同じように興味深いベンチマークが、こちらにあります。
http://pastebin.com/kbzgL8si
このベンチマークでは
popcnt
は、(偽の)依存関係の連鎖の中にある。
False Chain 0: 41959360000 0.57748 sec 18.1578 GB/s
False Chain 1: 41959360000 0.585398 sec 17.9122 GB/s
False Chain 2: 41959360000 0.645483 sec 16.2448 GB/s
False Chain 3: 41959360000 0.929718 sec 11.2784 GB/s
False Chain 4: 41959360000 1.23572 sec 8.48557 GB/s
関連
-
[解決済み】C++ - 解放されるポインタが割り当てられていないエラー
-
[解決済み】致命的なエラー LNK1169: ゲームプログラミングで1つ以上の多重定義されたシンボルが発見された
-
[解決済み】文字列関数で'char const*'のインスタンスを投げた後に呼び出されるterminate [閉店].
-
[解決済み】「corrupted size vs. prev_size」glibc エラーを理解する。
-
[解決済み】cc1plus:エラー:g++で認識されないコマンドラインオプション"-std=c++11"
-
[解決済み】オブジェクト引数のない非静的メンバ関数の呼び出し コンパイラーエラー
-
[解決済み】クラスのコンストラクタへの未定義参照、.cppファイルの修正も含む
-
[解決済み】C++ - 適切なデフォルトコンストラクタがない [重複]。
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】コンストラクターでのエラー:識別子を期待されますか?
-
[解決済み】デバッグアサーションに失敗しました。C++のベクトル添え字が範囲外
-
[解決済み】リンカーエラーです。"リンカ入力ファイルはリンクが行われていないため未使用"、そのファイル内の関数への未定義参照
-
[解決済み】指定範囲内の乱数で配列を埋める(C++)
-
[解決済み】デバッグアサーションに失敗しました
-
[解決済み] 32ビット整数のセットビットの数を数えるには?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] インテル Sandybridge ファミリー CPU のパイプラインのためのプログラムの最適化解除
-
[解決済み] レジスタを自分自身とXORする目的は何ですか?重複
-
[解決済み] 最下位ビットがセットされる位置