青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

C++ Coder

HCP高性能計算架構,實現,編譯器指令優化,算法優化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

C++博客 首頁 新隨筆 聯系 聚合 管理
  98 Posts :: 0 Stories :: 0 Comments :: 0 Trackbacks

#

最近在實現一個向量相稱的CUDA程序的時候,碰見一個讓我很頭疼的問題。基本癥狀是:計算結果在小數據量的時候是完全正確的,如果向量的維數增大到一定數值后,計算結果就不對了。我查了好久,終于找到了問題的源頭。竟然是數據類型的問題,寫下來提醒后來人!

我在內核函數中使用以下語句:

01.unsigned char tx = threadIdx.x;  
02.unsigned char bx = blockIdx.x;  
03.unsigned int id = tx + bx*blockDim.x;  

 

id竟然不對了。我需要ls個線程,如果ls的數目足夠大,使得bx的值溢出了。


解決方法是: 

 

unsigned short bx = blockIdx.x;

頭疼的解決總會是好的。歡迎大家的駐足,我繼續。。


http://blog.csdn.net/bendanban/article/details/7971447
posted @ 2012-10-21 12:56 jackdong 閱讀(499) | 評論 (0)編輯 收藏

問題描述:有很多朋友希望自己的MFC程序中能使用CUDA。就一直搜索MFC項目配置CUDA的資料。在這里以個人的經驗和理解向還在迷茫的朋友們說幾點,看完這幾點說明,相信大家不會再迷茫。

1、首先微軟提供過的各種項目模版只是簡化了我們的開發時間,它的實質還是C或者C++語言。所以我們只要把神馬MFC項目理解為普通的C\C++項目就可以了。

2、基于第一點,我們應該做些什么那?只要在我們的MFC項目下按照一般C\C++程序添加CUDA代碼就可以了。這一點我們應該思考,添加了代碼就可以了嗎?當然不可以!應該思考一下文件的編譯過程,在沒有指定編譯器的情況下你的CUDA程序怎么可能被正確編譯那?你必須選擇.cu文件的編譯規則!如果你使用runtime,你要添加自定義編譯規則,然后修改你的cu文件的屬性,告訴VS你的cu問價是誰來編譯的。

3、根據nvcc的編譯規則,cu文件首先會被編譯,然后生成一大堆的obj文件,然后是微軟的編譯器開始工作,他編譯了剩下的程序。然后是連接器,它連接了所有obj,lib。然后你如果想執行,執行就是了。

4、如果大家覺得這種方法不可靠,完全可以把cuda的程序寫成DLL的,然后在大家想使用CUDA的程序中添加lib、dll。

 

以上是我個人理解,如有可以探討的問題,可以評論,我會及時做出回答。微笑


http://blog.csdn.net/bendanban/article/details/7606116
posted @ 2012-10-21 12:54 jackdong 閱讀(442) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/7918391

本文以CUDA4.2為例講解如何在Ubuntu12.04上安裝CUDA

注意一點,在安裝之前,必須確保自己的GPU是NVIDIA的GPU,并且支持CUDA。如果不確定自己的顯卡是否支持CUDA,可以在http://developer.nvidia.com/cuda-gpus中找到支持CUDA的GPU列表。如果你的顯卡是ATI的,可以使用OpenCL來獲得GPU的計算資源(http://www.khronos.org/opencl).

 

如果你的GPU滿足上面的要求,下面我們就開始安裝了。

第一步:下載安裝文件,安裝文件driver,toolkit,SDK。可以在http://developer.nvidia.com/cuda/cuda-toolkit-archive下載到自己想安裝的版本。特別注意一下自己的系統是多少位的,在終端執行下面的命令可以知道你的系統的位數。

[]$uname -m

 

i686是32位系統,x86_64是64位系統。至于選擇那個toolkit選擇Ubuntu11.04是可行的,至少我使用了幾個月了沒遇到問題。

 

 

第二步:安裝驅動。

確保所有需要的東西都已經安裝好了。

 

$sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev binutils-gold

為了不讓系統打擾我們安裝驅動,把一些需要的模塊列入黑名單:

[]$gksu gedit /etc/modprobe.d/blacklist.conf

在打開的文件里添加一下幾行

blacklist amd76x_edac
blacklist vga16fb
blacklist nouveau
blacklist rivafb
blacklist nvidiafb
blacklist rivatv

將文件保存后退出。

 

 

為了能去除所有NVIDIA的殘余物,在終端中執行下面的命令:

sudo apt-get remove --purge nvidia*

這個命令可能需要執行一段時間,所以要耐心的等等。等它完成了,重新啟動你的機器。在登錄界面出現后,先不要登錄,在鍵盤上按下Ctrl+Alt+F1組合鍵,以文本的方式登錄,找到你的驅動安裝文件,執行下面的命令:

sudo service lightdm stop
chmod a
+x devdriver*.run

這里devdriver*.run指的是你的驅動的名字。下面是安裝。

sudo ./devdriver*.run

安裝結束后,重新啟動系統。

 

 

第三步:登錄后,打開終端,安裝toolkit

在終端下進入toolkit的目錄,執行下面的命令:

chmod a+x cudatoolkit*.run
sudo .
/cudatoolkit*.run

 

cudatoolkit*.run代表你的toolkit的安裝文件。

這樣安裝之后還要確保你安裝的動態鏈接庫可以被自己的程序找到。最好在使用CUDA之前執行下面的命令。

對于32位系統:

 

export LD_LIBRARY_PATH=/usr/local/cuda/lib:$LD_LIBRARY_PATH

對于64位系統:

export LD_LIVRARY_PATH=/usr/local/cuda/lib:$LD_LIBRARY_PATH
export LD_LIVRARY_PATH
=/usr/local/cuda/lib64:$LD_LIBRARY_PATH

第四步:安裝sdk。實際上,這一步并不是使用GPU計算的必要部分,這里面只是包含了一些有用的例子。不過還是推薦大家安裝這個SDK。

在終端下進入包含SDK的目錄,執行下面的命令

chmod a+x cudasdk.run
.
/cudasdk.run

 

cudasdk.run代表了你的SDK的安裝文件。

 

OK了,如果有問題,一定要留言奧。。。

 

PS:本文參考了一篇英文的文章,不過我沒有找到那篇文章,要是有網友找到了,可以給我留言。我加上對它的引用。







 



posted @ 2012-10-21 12:51 jackdong 閱讀(530) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/7673607
給程序計時對于程序員來說實在是太重要了,在windows上的那個clock()實在是不夠精確,精度只有10ms,真讓人難過。研究了下windows下使用C、C++計時的函數,給大家分享下。

主要就是兩個函數的使用。我先把一段可以運行的代碼貼出來,然后講講這兩個函數。

#include <windows.h>
#include 
<stdio.h>
int main(int argc, char **argv)
{
    LARGE_INTEGER freq;
    LARGE_INTEGER start_t, stop_t;
    
double exe_time;
    QueryPerformanceFrequency(
&freq);
    fprintf(stdout, 
"The frequency of your pc is %d.\n", freq.QuadPart);
    QueryPerformanceCounter(
&start_t);
    Sleep(
1000);
    QueryPerformanceCounter(
&stop_t);
    exe_time 
= 1e3*(stop_t.QuadPart-start_t.QuadPart)/freq.QuadPart;
    fprintf(stdout, 
"Your program executed time is %fms.\n", exe_time); 
    getchar();
    
return 0;
}

 

1、LARGE_INTEGER在微軟的編譯器中實際上是一個union,它的定義如下:
typedef union _LARGE_INTEGER
{  
    
struct
 
    
{    
        DWORD LowPart;   
        LONG HighPart;  
    }

    
struct
 
    
{   
        DWORD LowPart;    
        LONG HighPart;  
    }
 u;  
    LONGLONG QuadPart;
}
 LARGE_INTEGER,  *PLARGE_INTEGER;


如果你使用的編譯器支持64位整數,那么可以使用QuadPart來引用變量的值。如果你的編譯器不支持64位整數,那么可以使用LowPart和HighPart來引用64位整數的低32位和高32位。

2、QueryPerformanceFrequncy(LARGE_INTEGER *freq)

它用于獲得你的機器一秒鐘執行多少次,就是你的時鐘周期。

3、QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount)

它獲得的是CPU從開機以來執行的時鐘周期數。

 

O啦。。。。好好用用它吧。






posted @ 2012-10-21 12:45 jackdong 閱讀(540) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/7669624

總有些童鞋想知道怎么在CUDA中使用二維數組([M][N]這種類型),其實這個很簡單,不過你要完全弄明白,必須對指針,地址等概念非常清楚才行。寫這篇博客解決下大家這個問題:

1、首先講述一下在一般C語言中如何使用二維數組。

int r, c;
int **arr = (int**)malloc(ROWS*sizeof(int*));
int *data = (int*)malloc(COLS*ROWS*sizeof(int));
for (r = 0; r < ROWS; r++)
{
    arr[r] 
= data + r*COLS;
}


free(arr);
free(data);

 


 代碼中的arr實個二維數組變量了,你可以在for循環之后arr[i][j]的方式使用它。

 

2、告訴你如何在CUDA中使用二維數組可以類比1中的方法,不過你要清楚幾點,這幾點在代碼之后說明。

#include <stdio.h>
#include 
<stdlib.h>
#include 
<cuda_runtime.h>

#define ROWS 32
#define COLS 16
#define CHECK(res) if(res!=cudaSuccess){exit(-1);}
__global__ 
void Kerneltest(int **da, unsigned int rows, unsigned int cols)
{
    unsigned 
int row = blockDim.y*blockIdx.y + threadIdx.y;
    unsigned 
int col = blockDim.x*blockIdx.x + threadIdx.x;
    
if (row < rows && col < cols)
    
{
        da[row][col] 
= row*cols + col;
    }

}


int main(int argc, char **argv)
{
    
int **da = NULL;
    
int **ha = NULL;
    
int *dc = NULL;
    
int *hc = NULL;
    cudaError_t res;
    
int r, c;
    
bool is_right=true;

    res 
= cudaMalloc((void**)(&da), ROWS*sizeof(int*));CHECK(res)
    res 
= cudaMalloc((void**)(&dc), ROWS*COLS*sizeof(int));CHECK(res)
    ha 
= (int**)malloc(ROWS*sizeof(int*));
    hc 
= (int*)malloc(ROWS*COLS*sizeof(int));

    
for (r = 0; r < ROWS; r++)
    
{
        ha[r] 
= dc + r*COLS;
    }

    res 
= cudaMemcpy((void*)(da), (void*)(ha), ROWS*sizeof(int*), cudaMemcpyHostToDevice);CHECK(res)
    dim3 dimBlock(
16,16);
    dim3 dimGrid((COLS
+dimBlock.x-1)/(dimBlock.x), (ROWS+dimBlock.y-1)/(dimBlock.y));
    Kerneltest
<<<dimGrid, dimBlock>>>(da, ROWS, COLS);
    res 
= cudaMemcpy((void*)(hc), (void*)(dc), ROWS*COLS*sizeof(int), cudaMemcpyDeviceToHost);CHECK(res)

    
for (r = 0; r < ROWS; r++)
    
{
        
for (c = 0; c < COLS; c++)
        
{
            printf(
"%4d ", hc[r*COLS+c]);
            
if (hc[r*COLS+c] != (r*COLS+c))
            
{
                is_right 
= false;
            }

        }

        printf(
"\n");
    }

    printf(
"the result is %s!\n", is_right? "right":"false");
    cudaFree((
void*)da);
    cudaFree((
void*)dc);
    free(ha);
    free(hc);
    getchar();
    
return 0;
}

 


在CUDA中使用二維數組的幾點說明:

1)da是一個二維變量,一定更不可以在33行的時候把ha改成da!一定要記住顯存和內存是相互獨立的,主機端的程序不可以直接操作顯存!必須通過CUDA 提供的API函數來操作!

2)注意在內存申請時強制類型轉換(void**)(&),怎么把***的變量轉成**了!!這主要是API借口決定的,最好自己顯式轉換格式,避免不必要的麻煩。

3)看見數據拷貝的函數了嗎,類型、類型、還是類型。

4)別忘了釋放內存和顯存!看見沒,還是類型。

5)很希望這篇博客能幫到大家,可是我真的不推薦大家在GPU上使用二維數組!真的!!為什么呢?終歸是效率惹的禍!顯存的訪問總是慢的。二維訪存,可是連續訪問了兩次啊。要是老這樣做,不但執行效率低,而且寫代碼也慢。如果對內存的概念不熟悉,千萬別趟這趟渾水。看懂這段代碼,就當是學習一下或者理解下內存、顯存與內存獨立的概念和規則吧。

附上執行結果:


 


posted @ 2012-10-21 12:43 jackdong 閱讀(774) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/7753995
剛學了一招,可以使用編譯器的-D選項來定義程序中使用的宏。
#include <stdio.h>
int main(int argc, char **argv)
{
    #ifdef MY_MAC
    printf(
"Hello -D.\n");
    
#else 
    printf(
"MY_MAC was not defined.\n");
    
#endif
    
return 0;
}
上面的代碼中使用了MY_MAC宏,

 

【】$g++ -DMY_MAC -o dtest dtest.c

執行結果:

【】$ ./dtest 
Hello 
-D.
【】$

無掉-D選項,重新編譯,執行結果:

【】$ g++ ./dtest.c -o dtest
【】$ .
/dtest 
MY_MAC was not defined.

我們可以利用編譯器這個選項來調試我們的程序奧。

 

如果我的宏代表一個常量怎么辦呢??

看看修改后的代碼:

 

#include <stdio.h>
int main(int argc, char **argv)
{
    #ifdef MY_MAC
    printf(
"Hello -D. %d\n", MY_MAC);
    
#else 
    printf(
"MY_MAC was not defined.\n");
    
#endif
    
return 0;
}



我輸出了宏代表的值。

 

在終端執行一下命令:

 

【】$ g++ -DMY_MAC=5 ./dtest.c -o dtest
【】$ .
/dtest 
Hello 
-D. 5
【】$

如果程序中有多個宏可以這樣編譯

【】$g++ -DMAC1=5 -DMAC2=6 soucefile.c

^_^,,很興奮是吧??這樣我們就不用在代碼里修改宏變量了。。




posted @ 2012-10-21 12:41 jackdong 閱讀(323) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/7742593

在網上找了找關于Progfile的工具,找到了這篇文章覺得不錯,轉來分享下。對原文修改了下。

本文介紹了如何使用Gnu gprof 對Linux平臺下的現有程序進行優化分析和生成程序調用圖。主要偏重于對生成和使用流程圖作介紹.

Gprof 簡介:
Gprof功能:打印出程序運行中各個函數消耗的時間,可以幫助程序員找出眾多函數中耗時最多的函數。產生程序運行時候的函數調用關系,包括調用次數,可以幫助程序員分析程序的運行流程。
有了函數的調用關系,這會讓開發人員大大提高工作效率,不用費心地去一點點找出程序的運行流程,這對小程序來說可能效果不是很明顯,但對于有幾萬,幾十萬代碼量的工程來說,效率是毋庸置疑的!而且這個功能對于維護舊代碼或者是分析Open Source來說那是相當誘人的,有了調用圖,對程序的運行框架也就有了一個大體了解,知道了程序的“骨架“,分析它也就不會再那么茫然,尤其是對自己不熟悉的代碼和Open Source。費話不多說了,讓我們開始我們的分析之旅吧!

Gprof 實現原理:
通過在編譯和鏈接你的程序的時候(使用 -pg 編譯和鏈接選項),gcc 在你應用程序的每個函數中都加入了一個名為mcount ( or  “_mcount”  , or  “__mcount” , 依賴于編譯器或操作系統)的函數,也就是說你的應用程序里的每一個函數都會調用mcount, 而mcount 會在內存中保存一張函數調用圖,并通過函數調用堆棧的形式查找子函數和父函數的地址。這張調用圖也保存了所有與函數相關的調用時間,調用次數等等的所有信息。

Gprof基本用法:
1. 使用 -pg 編譯和鏈接你的應用程序。

2. 執行你的應用程序使之生成供gprof 分析的數據。

3. 使用gprof 程序分析你的應用程序生成的數據。

Gprof 簡單使用:
讓我們簡單的舉個例子來看看Gprof是如何使用的。

1.打開linux終端。新建一個test.c文件,并生用-pg 編譯和鏈接該文件。 test.c 文件內容如下:
#include "stdio.h"
#include 
"stdlib.h"
void a()
{
  printf(
"\t\t+---call a() function\n");
}

void c()
{
  printf(
"\t\t+---call c() function\n");
}

int b()
{
  printf(
"\t+--- call b() function\n");
  a();
  c();
  
return 0;
}


int main()
{
  printf(
" main() function()\n");
  b();
}

命令行里面輸入下面命令,沒加-c選項,gcc 會默認進行編譯并鏈接生成a.out:
$gcc -pg test.c

如果沒有編譯錯誤,gcc會在當前目錄下生成一個a.out文件,當然你也可以使用 –o 選項給生成的文件起一個別的名字,像 gcc –pg test.c –o test , 則gcc會生成一個名為test的可執行文件,在命令行下輸入[linux /home/test]$./test , 就可以執行該程序了,記住一定要加上 ./ 否則程序看上去可能是執行,可是什么輸出都沒有。

 

2.執行你的應用程序使之生成供gprof 分析的數據。  命令行里面輸入:

[linux /home/test]$a.out
main() function()
+--- call b() function
+---call a() function
+---call c() function

你會在當前目錄下看到一個gmon.out 文件, 這個文件就是供gprof 分析使用的。

 

3.使用gprof 程序分析你的應用程序生成的數據。
命令行里面輸入:

 

$ gprof -b a.out gmon.out | less

由于gprof輸出的信息比較多,這里使用了 less 命令,該命令可以讓我們通過上下方向鍵查看gprof產生的輸出,| 表示gprof -b a.out gmon.out 的輸出作為 less的輸入。下面是我從gprof輸出中摘抄出的與我們有關的一些詳細信息。

%     cumulative    self              self     total
time   seconds     seconds    calls  Ts
/call  Ts/call  name
0.00      0.00     0.00        1     0.00     0.00  a
0.00      0.00     0.00        1     0.00     0.00  b
0.00      0.00     0.00        1     0.00     0.00  c

Call graph

granularity: each sample hit covers 
4 byte(s) no time propagated

index 
% time    self  children    called     name
0.00    0.00       1/1           b [2]
[
1]      0.0    0.00    0.00       1         a [1]
-----------------------------------------------
0.00    0.00       1/1           main [10]
[
2]      0.0    0.00    0.00       1         b [2]
0.00    0.00       1/1           a [1]
0.00    0.00       1/1           c [3]
-----------------------------------------------
0.00    0.00       1/1           b [2]
[
3]      0.0    0.00    0.00       1         c [3]

從上面的輸出我們能明顯的看出來,main 調用了 b 函數, 而b 函數分別調用了a 和 c 函數。由于我們的函數只是簡單的輸出了一個字串,故每個函數的消耗時間都是0 秒。

 


常用的Gprof 命令選項解釋:

-b不再輸出統計圖表中每個字段的詳細描述。

-p 只輸出函數的調用圖(Call graph 的那部分信息)。

-q 只輸出函數的時間消耗列表。

-E Name不再輸出函數Name 及其子函數的調用圖,此標志類似于 -e 標志,但它在總時間和百分比時間的計算中排除了由函數Name 及其子函數所用的時間。

-e Name 不再輸出函數Name 及其子函數的調用圖(除非它們有未被限制的其它父函數)。可以給定多個 -e 標志。一個 -e 標志只能指定一個函數。

-F Name 輸出函數Name 及其子函數的調用圖,它類似于 -f 標志,但它在總時間和百分比時間計算中僅使用所打印的例程的時間。可以指定多個 -F 標志。一個 -F 標志只能指定一個函數。-F 標志覆蓋 -E 標志。

-f Name輸出函數Name 及其子函數的調用圖。可以指定多個 -f 標志。一個 -f 標志只能指定一個函數。

-z 顯示使用次數為零的例程(按照調用計數和累積時間計算)。

到這為止你可能對gprof 有了一個比較感性的認識了,你可能會問如何用它去分析一個真正的Open Source 呢!下面就讓我們去用gprof去分析一個Open Source,看看如何去在真實的環境中使用它。

使用Gprof 分析 Cflow開源項目
CFlow 是程序流程分析工具,該工具可以通過分析C源代碼,產生程序調用圖!有點跟Gprof差不多,不過CFlow是通過源代碼進行的靜態分析并且 不能分析C++ 程序,你可以到http://www.gnu.org/software/cflow/去下載源代碼。

假設你已經下載了該源代碼(cflow-1.1.tar.gz),并把它放置在/home目錄下,讓我們看看如何在這個應用上使用gprof。

1. 使用 -pg 編譯和鏈接該應用程序,請輸入下列命令。

 

[linux /home/]tar zxvf cflow-1.1.tar.gz
[linux /home/cflow-1.1/src]$./configure
[linux /home]$make CFLAGS=-pg LDFLAGS=-pg 

如果沒有出錯你會在/home/cflow-1.1/src 目錄下發行一個名為cflow的可執行文件,這就是我們加入-pg編譯選項后編譯出來的可以產生供gprof提取信息的可執行文件。記住一定要在編譯和鏈接的時候都使用-pg選項,否則可能不會產生gmon.out文件。對于cflow項目,CFLAGS=-pg 是設置它的編譯選項,LDFLAGS=-pg是設置它的鏈接選項。當然你也可以直接修改它的Makefile來達到上述相同的目的,不過一定要記住編譯和鏈接都要使用-pg選項。

 

2. 運行cflow 程序使之生成gmon.out 文件供gprof使用。

 

[linux /home/cflow-1.1/src]$cflow parser.c

查看/home/cflow-1.1/src目錄下有沒有產生gmon.out文件,如果沒有請重復第一步,并確認你已經在編譯和鏈接程序的時候使用了-pg 選項。Cflow的使用請參考http://www.gnu.org/software/cflow/manual/cflow.html

 

3. 使用gprof分析程序
[linux /home/cflow-1.1/src]$gprof -b cflow gmon.out | less
恭喜你,不出意外你會在屏幕上看到gprof的輸出,函數消耗時間和函數調用圖,下面是我從我的輸出中摘抄出來的一小段。

 

%   cumulative   self              self     total time   seconds   seconds    calls  Ts/call  Ts/call  name 0.00      0.00     0.00   118262     0.00     0.00  include_symbol 0.00      0.00     0.00    92896     0.00     0.00  is_printable 0.00      0.00     0.00    28704     0.00     0.00  set_level_mark 0.00      0.00     0.00    28703     0.00     0.00  is_last 0.00      0.00     0.00    19615     0.00     0.00  auto_processor 0.00      0.00     0.00    15494     0.00     0.00  gnu_output_handler 0.00      0.00     0.00    12286     0.00     0.00  delete_parm_processor 0.00      0.00     0.00     7728     0.00     0.00  newline 0.00      0.00     0.00     7728     0.00     0.00  print_function_name 0.00      0.00     0.00     7728     0.00     0.00  print_level

。。。。。。 。。。。。。

Call graph

granularity: each sample hit covers 4 byte(s) no time propagated

index % time    self  children   called     name [1]      0.0    0.00    0.00     79+855  [1] 0.00    0.00    166     dcl [52] 0.00    0.00    163     parse_dcl [53] 0.00    0.00    150     dirdcl [56] 0.00    0.00    129     parse_declaration [63] 0.00    0.00     98     parse_variable_declaration [66] 0.00    0.00     63     maybe_parm_list [69] 0.00    0.00     63    parse_function_declaration [70] 0.00    0.00     39    func_body [74]

。。。。。。 。。。。。。

 


通過分析%time你就知道了那個函數消耗的時間最多,你可以根據這個輸出信息做有目的的優化,不過cflow執行的速度是在是太快了,以至%time都是0 (消耗時間是以秒為單位進行統計的)。

 

生成圖形化的函數調用圖

1.Graphviz 工具
看到這里你也可能覺得上面的函數調用圖實在是不方便察看,也看不出來一個程序調用的整體框架。沒有關系,我再介紹一個有用的工具給你,使用 Graphviz,Graphviz or Graph Visualization 是由 AT&T 開發的一個開源的圖形可視化工具。它提供了多種畫圖能力,但是我們重點關注的是它使用 Dot 語言直連圖的能力。在這里,將簡單介紹如何使用 Dot 來創建一個圖形,并展示如何將分析數據轉換成 Graphviz 可以使用的規范, Dot 使用的圖形規范。

使用 Dot 語言,你可以指定三種對象:圖、節點和邊。為了讓你理解這些對象的含義,我將構建一個例子來展示這些元素的用法。


下圖給出了一個簡單的定向圖(directed graph),其中包含 3 個節點。第一行聲明這個圖為 G,并且聲明了該圖的類型(digraph)。接下來的三行代碼用于創建該圖的節點,這些節點分別名為 node1、node2 和 node3。節點是在它們的名字出現在圖規范中時創建的。邊是在在兩個節點使用邊操作(->)連接在一起時創建的,如第 6 行到第 8 行所示。我還對邊使用了一個可選的屬性 label,用它來表示邊在圖中的名稱。最后,在第 9 行完成對該圖規范的定義。
使用 Dot 符號表示的示例圖(test.dot)

 

digraph G {
    node1;
    node2;
    node3;

    node1 
-> node2 [label="edge_1_2"];
    node1 
-> node3 [label="edge_1_3"];
    node2 
-> node3 [label="edge_2_3"];
 }

要將這個 .dot 文件轉換成一個圖形映像,則需要使用 Dot 工具,這個工具是在 Graphviz 包中提供的。清單 6 介紹了這種轉換。
清單 6. 使用 Dot 來創建 JPG 映像
[linux /home]$ dot -Tjpg test.dot -o test.jpg
在這段代碼中,我告訴 Dot 使用 test.dot 圖形規范,并生成一個 JPG 圖像,將其保存在文件 test.jpg 中。所生成的圖像如圖1所示。在此處,我使用了 JPG 格式,但是 Dot 工具也可以支持其他格式,其中包括 GIF、PNG 和 postscript等等。

 


Dot創建的實例圖

Dot 語言還可以支持其他一些選項,包括外形、顏色和很多屬性。有興趣可以查看graphviz相關文檔。
2.從gprof的輸出中提取調用圖信息,產生可供Graphviz使用的dot文件。
這樣的腳本有人已經實現了,我們只要下載一個現成的就可以了,首先從http://www.ioplex.com/~miallen/ 網站下載一個mkgraph腳本。解壓該腳本到包含gmon.out文件的目錄下。使用mkgraph0.sh產生調用的jpg圖像文件。例如:使用上面的例子,生成cflow的調用圖。
[linux /home/cflow-1.1/src]$ mkgraph0.sh cflow gmon.out
部分調用圖如下,有了這個圖是不是對程序整體框架有了個清晰地了解,如果你對生成的調用圖效果不滿意,你還可以通過修改mkgraph0腳本使之產生合適的dot文件即可:

總結:
使用gprof , Graphviz , mkgraph 生成函數調用圖
1. 使用 -pg 編譯和鏈接你的應用程序。
2. 執行你的應用程序使之生成供gprof 分析的數據。
3. 使用mkgraph腳本生成圖形化的函數調用圖。

相關資料:
文檔:用 Graphviz 可視化函數調用
文檔:Speed your code with the GNU profiler
文檔:gropf 幫助文件
Mkgraph 腳本:http://www.ioplex.com/~miallen/
Graphviz 工具:http://www.graphviz.org
Cflow         :http://www.gnu.org/software/cflow/
(責任編輯:城塵 68476636-8003)

原文地址:點擊打開鏈接http://os.51cto.com/art/200703/41426_2.htm








posted @ 2012-10-21 12:36 jackdong 閱讀(528) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/6303282

在上一節中我們講的是一個常用的并行化編程方法(for的并行化),其實它只是并行化編程的一個特例,只是它的地位較高,或者說它比其它并行化更重要。在本節中我們將討論一般的并行區域編程。

并行區域的編譯指導語句一般格式

    一般格式:#pragma omp parallel [clause[clause]…]{…}

    其中{…}中為每個線程都執行的部分,在parallel后面可以跟隨一些指導子句,例如:threadprivate、copyin等,本節中將以一些實例來依次講解這些語句的作用。

    時刻記住,主線程就是0號線程,這個與for不同,另外private、firstprivate、lastprivate是for并行的東西最好不要拿來比較,盡管我在本文里比較了,但是我還是感覺有些頭痛啊。

例子實例

例1 不帶指導子句的并行程序

      #pragma omp parallel指示它下面的一對大括號內的程序復制執行threads個數次。默默人情況下,所有并行區域中的變量是共享的,所以一定要謹防數據競爭的發生。


 

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    #pragma omp parallel 
    
for (int i = 0; i < 2; i++
    

        printf(
"Hello World! i = %d, Thread Num = %d/n", i, omp_get_thread_num()); 
    }
 
    
return 0
}

 

image

圖1、例1的執行結果

例1的執行結果可以看出四個線程分別執行了一遍#pragma omp parallel下面的語句,想想一下如果使用#pragma omp parallel for會是什么樣的結果。

例2 使用threadprivate子句

     此命令表示所有并行線程使用指定變量為各自私有的,能被定義為各線程私有變量的變量只能是靜態變量和全局變量。看下面的程序,希望你能發現,其實#pragma omp threadprivate()是可以單獨使用的。

    說明一下:被定義為threadprivate的變量對于每個線程永遠是活的,你在任何時候再次重新啟動各線程時,前面并行時最后得到的變量值仍然保留他的值,如果你用private代替threadprivate,那么你重新啟動各線程時,變量的值仍然為0。如果你不初始化要聲明為private的變量,那么在串行程序中,你就不能再沒有任何賦值的情況下使用它,否則會引起異常的(VS2010)。而且private不能單獨像threadprivate那樣定義某個變量。

    如果你使用另外#pragma omp threadprivate(var),那么var必須是靜態變量或者全局變量,如過你在并行開始之前并沒有初始化賦值,那么每個線程中默認var的值為0.如果你賦了初值,那么每個線程中var的初值都是你賦的初始值。其中主線程就是0號線程。如果你使用了#pragma omp private(var),那么var并不會賦初值,即使你在并行之前賦了初值。想想原先#pragma omp parallel for 的firstprivate就知道了,呵呵。在#pragma omp parallel中可以使用lastprivate,但是不能使用lastprivate,使用firstprivate(var)后,串行部分的var并不是并行時0號線程的var值,它仍未你原先賦的初值。是不是很繞啊?呵呵,你可以這樣理解,使用firstprivate和private的并行都不是純真的聯合主線程的編號。這樣就混不了了。

 

#include 
#include 
"omp.h" 
int sum = 0
#pragma omp threadprivate(sum) 
int main(int argc, char* argv[]) 

    #pragma omp parallel 
    

        sum 
+= omp_get_thread_num(); 
        printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    }
 
    printf(
"/nserial sum = %d; thread num = %d/n/n", sum, omp_get_thread_num()); 
    #pragma omp parallel 
    printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    
return 0
}



 

image

圖2、例2執行結果

threadprivate指定了sum這個變量屬于每個線程,每個線程都有自己的sum變量,各個線程結束后他們的sum變量并沒有注銷,在此啊執行時,他們各自的sum值還保持原值。

例3 使用copyin

指定主線程的值拷貝到各線程中去,下面的例子中,sum是每個線程獨有的,并且初始值都是0,main中第一行代碼將0號線程的sum值賦為100,其它線程的sum值并沒有變。

 

#include <stdio.h>
#include 
"omp.h" 
int sum; 
#pragma omp threadprivate(sum) 
int main(int argc, char* argv[]) 

    sum 
= 100
    #pragma omp parallel copyin(sum) 
    

        sum 
+= omp_get_thread_num(); 
        printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    }
 
    printf(
"/nserial sum = %d; thread num = %d/n/n", sum, omp_get_thread_num()); 
    #pragma omp parallel 
    printf(
"parallel sum = %d; thread num = %d/n", sum, omp_get_thread_num()); 
    
return 0
}



 

image

圖3、例3的執行結果

image

圖4、例3中將copyin(sum)刪除后的執行結果

通過以上圖3、4可以看出copyin的用處是初始化各個線程中自己私有的sum變量。實際上定義sum為全局量時的初始值就是原sum值(如果不使用copyin的話)。

我想了想threadprivate和private以及firstprivate的區別。寫出來大家討論下。

1、threadprivate,限制變量為每個線程私有。被限制的變量必須具有全局特性,他的生命周期是整個程序。

2、private,可以限制變量為每個線程私有,但是他的生命周期是一次啟動并行計算。

3、firstprivate,可以將穿行程序中的初值帶進每個線程,變量為每個線程私有。生命周期與private相同。

4、還有個lastprivate的問題,他并不能在區域并行中使用。

大家實驗把。。。

posted @ 2012-10-21 11:55 jackdong 閱讀(388) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/6303100

   openMP并不是只能對循環來并行的,循環并行化單獨拿出來說是因為它在科學計算中非常有用,比如向量、矩陣的計算。所以我單獨拿出這一部分給大家講講。這里主要講解的是for循環。

編譯指導語句:

    一般格式:

    #pragma omp parallel for [clause[clause…]]

    for(index = first; qualification; index_expr)

    {…}

    第一句中[]的部分是可選的,由自己的程序并行特點而定。大家先不要把精力放到這里面。后面的文章中會繼續講解的。

并行化for的編寫規則

    1、index的值必須是整數,一個簡單的for形式:for(int i = start; i < end; i++){…} 。

    2、start和end可以是任意的數值表達式,但是它在并行化的執行過程中值不能改變,也就是說在for并行化執行之前,編譯器必須事先知道你的程序執行多少次,因為編譯器要把這些計算分配到不同的線程中執行。

    3、循環語句只能是單入口但出口的。這里只要你避免使用跳轉語句就行了。具體說就是不能使用goto、break、return。但是可以使用continue,因為它并不會減少循環次數。另外exit語句也是可以用的,因為它的能力太大,他一來,程序就結束了。

例子講解

例1、for循環并行:

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
int i; 
    #pragma omp parallel 
for 
    
for (i = 0; i < 12; i++
    
{        printf("i = %d  %d/n", i, omp_get_thread_num());    } 
    
return 0
}

 

例1的執行結果如圖1所示:

image

圖1、例1的執行結果

從結果中可以看出 i 屬于{0,1,2}時由0號線程執行,i 屬于{3,4,5}時由1號線程執行,i 屬于{6,7,8}時由2號線程執行,i 屬于{9,10,11}時由3號線程執行。omp_get_thread_num()這個函數通過執行結果大家也知道了,他返回每個線程的編號。

并行編譯子句

    openMP中有多種并行化子句,這些子句都是為控制循環并行化編譯而設定的,這里我們主要關注數據作用域子句,這里的數據作用域是指各個線程是否對某一變量有權訪問。shared子句用來標記變量在各個線程之間是共享的,private子句標記變量在各個線程之間是私有的,實際上它會在在每個線程中保存一個副本。默認情況下,并行執行的變量是共享的。至于其它編譯子句將在后面的文章中介紹。

用實例講解數據作用域子句

實際上我很難想到一個綜合的例子來講解這種子句的限制異同,所以我寫了幾個例子。

例2、private

#include
#include "omp.h"
int main(int argc, char* argv[])
{
    float x = 4.3f;
    int i;
    #pragma omp parallel for private(x)
    for (i = 0; i < 12; i++)
    {
        x = 0;
        printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
    }
    printf("/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());
    return 0;
}

image

圖2、例2執行結果

例3 firstprivate(var):指定var在每個線程中都有一個副本,并且var的初始值在并行執行開始之前定義,每個并行線程的var的副本初值就是串行時定義的初始值。程序結束后串行程序中的var值并不會改變。

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 4.3f
    
int i; 
    #pragma omp parallel 
for firstprivate(x) 
    
for (i = 0; i < 12; i++
    

        x 
+= 1.0f
        printf(
"parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    }
 
    printf(
"/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    
return 0
}

 

 

image

圖3、例3的執行結果

例4 lastprivate(var):指定最后多線程執行完后原串行循環最后一次var的值帶到主線程(串行部分)

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 4.3f
    
int i; 
    #pragma omp parallel 
for lastprivate(x) 
    
for (i = 0; i < 12; i++
    
{        
        x 
= 0.0f
        printf(
"parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    }
 
    printf(
"/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    
return 0
}

image

圖4、例4的執行結果

例5 firstprivate與lastprivate聯用,很奇怪openMP很多情況下是不允許某個變量被指定兩次規則的,他倆卻可以,呵呵,而且配合效果還不錯。

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 4.3f
    
int i; 
    #pragma omp parallel 
for firstprivate(x) lastprivate(x) 
    
for (i = 0; i < 12; i++
    
{        
        x 
+= (float)omp_get_thread_num(); 
        printf(
"parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    }
 
    printf(
"/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    
return 0
}

image

圖5、例5的執行結果

    從上面的例2、3的程序中可以看出例2中每個線程中x都是私有的,它屬于每個線程,在主線程的定義并不能帶入到各個線程中,使用firstprivate后,x在主線程的初始值可以帶到各個線程中,在圖3可以看出每個線程x的輸出結果實際是相同的,但是在并行執行結束后,主線程中的x值仍然為4.3。從例4的執行結果可以看出最后x的值帶出到了主線程中,這個x值到底是哪個線程中的哪?答案是最后一句x賦值后的值,哪個線程執行完的最晚就是哪個x的值。例5顯示firstprivate與lastprivate聯合使用的執行結果。

例6 reduction規約操作,

    執行reduction的變量要特別注意,以reduction(+:sum)為例。

    第一種情況:sum為局部變量。這是你必須為sum在串行程序中賦初值,sum 被設置為每個線程私有,先各自執行完算出各自的sum值,最后主線程會將 《線程數+1》個sum變量規約,比如你num_thread(4),在開始并行執行之前你對規約變量賦初值為10,并行時假設每個線程算的的sum值為1,那么最終sum帶到串行程序中的變量值為14(串行的10+四個線程的1)。

    第二種情況:sum為全局變量。這是你不必為sum賦初始值,因為此時默認串行的sum值為0,進入每個線程的sum值也是0,規約時仍然是將《線程數+1》個sum值相加,因為你并沒有對全局的sum賦初值,所以最后規約的結果看著像是只有各線程的sum參加了規約操作。其實當你將全局的sum賦初值時,你會發現最后規約的sum值又多加了全局變量sum的串行程序結果。

    重要提醒:不管你怎樣設計sum的串行聲明形式,只要他在被定義為規約變量,每次進入并行線程的sum值都是0;

    也許你想把每個并行線程的sum值初始化成一個非0的值,然后再各自線程中在使用,那么我可以告訴你,別想了(至少我沒有做到)。因為我規約sum值,如果這個規約有意義你的每個線程應該是各自獨立未回各自的sum的,那么這個初始值使用0就已經非常好了,因為各自的sum計算如果結果一樣,你為何不直接用一句乘法哪(線程數*一個線程計算的sum值)。

#include 
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    
float x = 0.0f
    
int i; 
    
float sum = 0.0f
#pragma omp parallel 
for private(x) reduction(+:sum) 
    
for (i = 0; i < 12; i++
    
{        
        x 
= (float)omp_get_thread_num(); 
        sum 
+= x; 
        printf(
"parallel sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num());        
    }
 
    printf(
"/nserial   sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num()); 
    
return 0
}

image

圖6、例6執行結果

    在例6中我使用了reduction(+:sum),這表示每個線程對sum這個共享變量執行加操作時其它任何線程不能對它進行加操作,實際上我們這樣理解是有偏差的,真正的機理在執行結果中不難看出,實際每個線程都拷貝了一個sum的副本,先在自己執行時加完sum,等所有線程都執行結束后,主線程再將每個線程的sum副本的值加起來返回給主線程中sum。

小結

    本節主要講述了for語句的并行化。現在為止大家應該可以熟練使用for并行化了。文章中可能還有些不全面的地方,熱切期望各位讀者能給出批評和指正,期待中……


posted @ 2012-10-21 11:54 jackdong 閱讀(624) | 評論 (0)編輯 收藏

http://blog.csdn.net/bendanban/article/details/6302857

 在學習并行編程之前,你應該知道進程、線程、主線程、從線程等基本概念。進程是一個大型應用程序的基本單位,在任務管理器里進程都有一個名稱,后面跟隨的是與他有關的資源。線程是程序執行的基本單位,它必須從屬與一個進程,一個進程可以有多個線程,同一個進程的線程可以共享進程的資源,例如他們可以引用同一個變量的值。一個進程一般會與一個.EXE文件關聯,所以我把程序和進程不加區分。一個程序中有多個線程時,它必然會有一個主線程,主線程執行完后,其它從線程也應該結束執行。

    并行化編程一般可以理解為多個線程的創建和并行化編程,并行化編程的東西很多,但他們都會有兩個必須的規定:1、程序執行模型。2、存儲模型。

    程序執行模型,他規定了并行化線程的執行方式,規則,或者說邏輯結構。openMP的執行采用了Fork-Join模型。主線程在執行過程中遇到要并行處理的部分,根據openMP的編譯指導語句來創建,執行多個線程,創建的線程個數一般與計算機的核心數成正比,可以通過添加一個環境變量(OMP_NUM_THREADS)來規定創建線程的個數,注意環境變量添加后要注銷或者重啟系統才會生效。

image

圖1 omp程序執行模型

    存儲模型,omp針對的是一個計算機或者分布式計算機的并行,在一臺計算機上他采用共享存儲的方式,多個線程共享一塊進程的內存資源。

    下面先寫個程序例子,能讓大家有個初步認識。這個程序是在VS2008中編譯的,項目類型為Win32ConsoleApplication。

例1、并行HelloWorld程序:

#include <stdio.h>
#include 
"omp.h" 
int main(int argc, char* argv[]) 

    printf(
"Hello World! Serial Begin./n"); 
    #pragma omp parallel    
//開始并行執行 
    {        printf("Hello World! Parallel/n");    } 
    printf(
"Hello World! Serial again./n"); 
    
return 0
}

 

此程序編譯之前,還需要你對你的編譯器項目屬性設置一下。這里我們以VS2008為例,首先設置項目支持openMP。右擊項目->屬性->C/C++->語言->openMP支持修改為是,如圖2所示,然后代碼生成修改為多線程調試,如圖3所示。執行結果如圖4所示。

image

圖2、添加openMP支持

image

圖3、多線程調試支持

image

圖4、執行結果

以上程序我并沒有設置環境變量,因為我的計算機是雙核的,所以他的并行部分輸出了兩行Hello World! Parallel,這說明他有兩個線程執行并行部分,每個線程完全執行了相同的一段程序。我們在設置一下環境變量后在執行一下。順便說明一下怎樣設置環境變量。

計算機右擊-》屬性-》高級-》環境變量-》系統變量-》新建。。。如圖5所示。

image

圖5、Win7環境下設置環境變量

設置完環境后,注銷或重啟系統后,再次執行例1的程序后得到的結果中Hello World! Parallel 將被執行四遍。因為你已經設置了四個線程了。

到現在為止,大家可以模仿著例1寫幾個小程序了,可是還有一句話大家可能還不大明白吧,#pragma omp parallel這句話標記{}中的程序將在OMP_NUM_THREADS個線程中執行。

在下面的幾篇文章中我將繼續講解openMP編程的基礎知識。歡迎繼續關注。


posted @ 2012-10-21 11:49 jackdong 閱讀(392) | 評論 (0)編輯 收藏

僅列出標題
共10頁: First 2 3 4 5 6 7 8 9 10 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美一区观看| 欧美亚洲三区| 欧美国产日韩一二三区| 亚洲欧美欧美一区二区三区| 亚洲美女视频在线观看| 亚洲精品国产精品国自产观看| 久久人人爽人人爽爽久久| 久久久之久亚州精品露出| 久久激情中文| 久色成人在线| 亚洲国产精品久久久久婷婷老年 | 久久九九免费视频| 在线日韩av片| 国产精品―色哟哟| 久久免费黄色| 欧美一区不卡| 久久国产直播| 亚洲综合99| 国产精品推荐精品| 影院欧美亚洲| 亚洲激情一区二区| 亚洲免费网址| 另类图片国产| 亚洲欧洲一区| 亚洲伊人观看| 免费精品99久久国产综合精品| 欧美激情一区二区三区在线视频观看 | 国产精品久久久久999| 国产精品免费视频观看| 一区二区视频欧美| 在线中文字幕不卡| 久久综合国产精品台湾中文娱乐网| 亚洲福利视频一区二区| 午夜激情综合网| 欧美精品色综合| 国产欧美精品日韩| 99精品国产高清一区二区| 久久久久高清| 中文精品一区二区三区| 老牛嫩草一区二区三区日本 | 久久综合九色综合欧美狠狠| 亚洲精品孕妇| 免费在线亚洲欧美| 国产日韩欧美在线一区| 亚洲网在线观看| 欧美激情中文字幕乱码免费| 欧美在线免费观看亚洲| 久久精品最新地址| 国色天香一区二区| 香蕉久久夜色| 亚洲国产精品尤物yw在线观看| 亚洲一级一区| 欧美xxx成人| 亚洲二区在线观看| 欧美一区二区免费| 国产精品国产三级国产专区53 | 国产三级欧美三级日产三级99| 亚洲欧洲另类国产综合| 久久最新视频| 久久久久久穴| 在线看国产日韩| 久久综合久久综合久久综合| 亚洲一区二区三区精品在线| 欧美网站大全在线观看| 正在播放亚洲| 99精品欧美一区二区三区综合在线| 欧美国产日本| 日韩写真在线| 夜夜精品视频| 国产精品一香蕉国产线看观看 | 揄拍成人国产精品视频| 老司机aⅴ在线精品导航| 久久女同精品一区二区| 亚洲国产精品视频一区| 亚洲国产成人久久综合一区| 欧美成人一区二免费视频软件| 亚洲欧洲在线一区| 亚洲精品国久久99热| 欧美日韩伦理在线| 午夜精品在线观看| 欧美一区日韩一区| 亚洲国产精品悠悠久久琪琪| 亚洲电影av在线| 欧美日韩一区二区三区四区在线观看 | 亚洲国产精品第一区二区| 欧美精品免费在线| 亚洲一区激情| 久久精品国产99| 国产日韩亚洲欧美精品| 欧美激情自拍| 国产日韩av在线播放| 亚洲国产精品va在看黑人| 国产亚洲欧美日韩精品| 夜色激情一区二区| 最新日韩欧美| 欧美高清在线播放| 国产精品美腿一区在线看| 一个色综合导航| 午夜亚洲福利| 亚洲欧美另类国产| 欧美午夜在线视频| 亚洲精品国产精品国自产观看浪潮| 国产一区二区在线观看免费播放| 午夜免费久久久久| 久久精品99国产精品| 欧美精品七区| 免费观看一级特黄欧美大片| 欧美一区二区成人6969| 久久福利电影| 亚洲欧美激情视频| 欧美大秀在线观看| 久久久久久一区二区| 欧美日韩一级片在线观看| 欧美成人久久| 国内外成人在线| 中文精品视频| 在线视频欧美日韩| 欧美va天堂| 毛片一区二区| 国产日韩一区二区三区在线| 日韩一区二区电影网| 91久久在线| 久久久久看片| 久久久蜜臀国产一区二区| 欧美少妇一区| 日韩一区二区精品视频| 亚洲免费电影在线观看| 久久先锋资源| 乱码第一页成人| 精品999成人| 欧美在线播放| 欧美在线亚洲| 国产精品一区免费视频| 一区二区三区欧美成人| 一区二区三区四区五区视频| 欧美三级视频在线| 欧美三日本三级少妇三99| 久久精品国产欧美激情| 亚洲精品欧美专区| 欧美高清视频www夜色资源网| 亚洲一区二区三区欧美| 欧美黑人多人双交| 亚洲综合电影一区二区三区| 一区二区三区欧美激情| 亚洲免费影视| 免播放器亚洲一区| 国产精品一区二区三区久久久| 亚洲精品韩国| 欧美成人一区在线| 久久久久久欧美| 狠狠色狠色综合曰曰| 久久不射网站| 嫩草影视亚洲| 亚洲日本激情| 欧美另类99xxxxx| 99国产精品国产精品毛片| 亚洲尤物在线视频观看| 国产麻豆精品theporn| 久久久久久一区| 欧美黄色免费| 亚洲一区二三| 黄色日韩网站视频| 欧美成人亚洲成人| 亚洲调教视频在线观看| 久久精品女人的天堂av| 亚洲精品国产无天堂网2021| 欧美午夜精品伦理| 久久国产日韩欧美| 亚洲高清影视| 欧美一区免费| 亚洲乱码国产乱码精品精可以看 | 99精品国产热久久91蜜凸| 欧美亚男人的天堂| 久久久久久9| 99国产精品国产精品久久| 久久国产免费| 99视频在线精品国自产拍免费观看| 国产精品乱码一区二三区小蝌蚪| 久久精品一级爱片| 亚洲视频一起| 欧美国产日本| 欧美在线播放一区| 99视频+国产日韩欧美| 国内精品久久久久伊人av| 欧美激情一区二区三区在线| 欧美一级专区| 一区二区三区欧美| 亚洲国产精品久久久| 久久精品一区二区三区不卡| 一本色道久久综合亚洲精品高清| 国产一区二区三区视频在线观看 | 亚洲国产成人av好男人在线观看| 欧美一区二区三区在线看| 亚洲春色另类小说| 久久综合一区二区三区| 一本色道久久88综合亚洲精品ⅰ| 欧美黑人多人双交| 欧美日韩亚洲在线| 亚洲天堂av图片| 久久精品91久久香蕉加勒比|