CUDA GPU編程

原文鏈接:https://zhuanlan.zhihu.com/p/77307505
Python是當(dāng)前最流行的編程語言俏险,被廣泛應(yīng)用在深度學(xué)習(xí)、金融建模纽甘、科學(xué)和工程計算上充石。作為一門解釋型語言廷臼,它運(yùn)行速度慢也常常被用戶詬病。著名Python發(fā)行商Anaconda公司開發(fā)的Numba庫為程序員提供了Python版CPU和GPU編程工具,速度比原生Python快數(shù)十倍甚至更多彼棍。使用Numba進(jìn)行GPU編程灭忠,你可以享受:

Python簡單易用的語法;

極快的開發(fā)速度座硕;

成倍的硬件加速弛作。

為了既保證Python語言的易用性和開發(fā)速度,又達(dá)到并行加速的目的华匾,本系列主要從Python的角度給大家分享GPU編程方法映琳。關(guān)于Numba的入門可以參考我的另一篇文章更加令人興奮的是蜘拉,Numba提供了一個GPU模擬器萨西,即使你手頭暫時沒有GPU機(jī)器,也可以先使用這個模擬器來學(xué)習(xí)GPU編程旭旭!

本系列為英偉達(dá)GPU入門介紹的第二篇谎脯,主要介紹CUDA編程的基本流程和核心概念,并使用Python Numba編寫GPU并行程序持寄。為了更好地理解GPU的硬件架構(gòu)穿肄,建議讀者先閱讀我的第一篇文章。

GPU硬件知識和基礎(chǔ)概念:包括CPU與GPU的區(qū)別际看、GPU架構(gòu)咸产、CUDA軟件棧簡介。

GPU編程入門:主要介紹CUDA核函數(shù)仲闽,Thread脑溢、Block和Grid概念,并使用Python Numba進(jìn)行簡單的并行計算赖欣。

GPU編程進(jìn)階:主要介紹一些優(yōu)化方法屑彻。

GPU編程實(shí)踐:使用Python Numba解決復(fù)雜問題。

初識GPU編程

兵馬未動顶吮,糧草先行社牲。在開始GPU編程前,需要明確一些概念悴了,并準(zhǔn)備好相關(guān)工具搏恤。

CUDA是英偉達(dá)提供給開發(fā)者的一個GPU編程框架,程序員可以使用這個框架輕松地編寫并行程序湃交。本系列第一篇文章提到熟空,CPU和主存被稱為主機(jī)(Host),GPU和顯存(顯卡內(nèi)存)被稱為設(shè)備(Device)搞莺,CPU無法直接讀取顯存數(shù)據(jù)息罗,GPU無法直接讀取主存數(shù)據(jù),主機(jī)與設(shè)備必須通過總線(Bus)相互通信才沧。

GPU和CPU架構(gòu)

在進(jìn)行GPU編程前迈喉,需要先確認(rèn)是否安裝了CUDA工具箱绍刮,可以使用echo $CUDA_HOME檢查CUDA環(huán)境變量,返回值不為空說明已經(jīng)安裝好CUDA挨摸。也可以直接用Anaconda里的conda命令安裝CUDA:

$ conda install cudatoolkit

然后可以使用nvidia-smi命令查看顯卡情況孩革,比如這臺機(jī)器上幾張顯卡,CUDA版本油坝,顯卡上運(yùn)行的進(jìn)程等嫉戚。我這里是一臺32GB顯存版的Telsa V100機(jī)器刨裆。

nvidia-smi命令返回結(jié)果

安裝Numba庫:

$ conda install numba

檢查一下CUDA和Numba是否安裝成功:

fromnumbaimportcudaprint(cuda.gpus)

如果上述步驟沒有問題澈圈,可以得到結(jié)果:<Managed Device 0>...。如果機(jī)器上沒有GPU或沒安裝好上述包帆啃,會有報錯瞬女。CUDA程序執(zhí)行時會獨(dú)霸一張卡,如果你的機(jī)器上有多張GPU卡努潘,CUDA默認(rèn)會選用0號卡诽偷。如果你與其他人共用這臺機(jī)器,最好協(xié)商好誰在用哪張卡疯坤。一般使用CUDA_VISIBLE_DEVICES這個環(huán)境變量來選擇某張卡报慕。如選擇5號GPU卡運(yùn)行你的程序。

CUDA_VISIBLE_DEVICES='5'python example.py

如果手頭暫時沒有GPU設(shè)備压怠,Numba提供了一個模擬器眠冈,供用戶學(xué)習(xí)和調(diào)試,只需要在命令行里添加一個環(huán)境變量菌瘫。

Mac/Linux:

exportNUMBA_ENABLE_CUDASIM=1

Windows:

SETNUMBA_ENABLE_CUDASIM=1

需要注意的是蜗顽,模擬器只是一個調(diào)試的工具,在模擬器中使用Numba并不能加速程序雨让,有可能速度更慢雇盖,而且在模擬器能夠運(yùn)行的程序,并不能保證一定能在真正的GPU上運(yùn)行栖忠,最終還是要以GPU為準(zhǔn)崔挖。

有了以上的準(zhǔn)備工作,我們就可以開始我們的GPU編程之旅了庵寞!

GPU程序與CPU程序的區(qū)別

一個傳統(tǒng)的CPU程序的執(zhí)行順序如下圖所示:

CPU程序執(zhí)行流程

CPU程序是順序執(zhí)行的虚汛,一般需要:

初始化。

CPU計算皇帮。

得到計算結(jié)果卷哩。

在CUDA編程中,CPU和主存被稱為主機(jī)(Host)属拾,GPU被稱為設(shè)備(Device)将谊。

GPU程序執(zhí)行流程

當(dāng)引入GPU后冷溶,計算流程變?yōu)椋?/p>

初始化,并將必要的數(shù)據(jù)拷貝到GPU設(shè)備的顯存上尊浓。

CPU調(diào)用GPU函數(shù)逞频,啟動GPU多個核心同時進(jìn)行計算。

CPU與GPU異步計算栋齿。

將GPU計算結(jié)果拷貝回主機(jī)端苗胀,得到計算結(jié)果。

一個名為gpu_print.py的GPU程序如下所示:

fromnumbaimportcudadefcpu_print():print("print by cpu.")@cuda.jitdefgpu_print():# GPU核函數(shù)print("print by gpu.")defmain():gpu_print[1,2]()cuda.synchronize()cpu_print()if__name__=="__main__":main()

使用CUDA_VISIBLE_DEVICES='0' python gpu_print.py執(zhí)行這段代碼瓦堵,得到的結(jié)果為:

print by gpu.

print by gpu.

print by cpu.

與傳統(tǒng)的Python CPU代碼不同的是:

使用from numba import cuda引入cuda庫

在GPU函數(shù)上添加@cuda.jit裝飾符基协,表示該函數(shù)是一個在GPU設(shè)備上運(yùn)行的函數(shù),GPU函數(shù)又被稱為核函數(shù)菇用。

主函數(shù)調(diào)用GPU核函數(shù)時澜驮,需要添加如[1, 2]這樣的執(zhí)行配置,這個配置是在告知GPU以多大的并行粒度同時進(jìn)行計算惋鸥。gpu_print[1, 2]()表示同時開啟2個線程并行地執(zhí)行g(shù)pu_print函數(shù)杂穷,函數(shù)將被并行地執(zhí)行2次。下文會深入探討如何設(shè)置執(zhí)行配置卦绣。

GPU核函數(shù)的啟動方式是異步的:啟動GPU函數(shù)后耐量,CPU不會等待GPU函數(shù)執(zhí)行完畢才執(zhí)行下一行代碼。必要時滤港,需要調(diào)用cuda.synchronize()廊蜒,告知CPU等待GPU執(zhí)行完核函數(shù)后,再進(jìn)行CPU端后續(xù)計算蜗搔。這個過程被稱為同步劲藐,也就是GPU執(zhí)行流程圖中的紅線部分。如果不調(diào)用cuda.synchronize()函數(shù)樟凄,執(zhí)行結(jié)果也將改變聘芜,"print by cpu.將先被打印。雖然GPU函數(shù)在前缝龄,但是程序并沒有等待GPU函數(shù)執(zhí)行完汰现,而是繼續(xù)執(zhí)行后面的cpu_print函數(shù),由于CPU調(diào)用GPU有一定的延遲叔壤,反而后面的cpu_print先被執(zhí)行瞎饲,因此cpu_print的結(jié)果先被打印了出來。

Thread層次結(jié)構(gòu)

前面的程序中炼绘,核函數(shù)被GPU并行地執(zhí)行了2次嗅战。在進(jìn)行GPU并行編程時需要定義執(zhí)行配置來告知以怎樣的方式去并行計算,比如上面打印的例子中,是并行地執(zhí)行2次驮捍,還是8次疟呐,還是并行地執(zhí)行20萬次,或者2000萬次东且。2000萬的數(shù)字太大启具,遠(yuǎn)遠(yuǎn)多于GPU的核心數(shù),如何將2000萬次計算合理分配到所有GPU核心上珊泳。解決這些問題就需要弄明白CUDA的Thread層次結(jié)構(gòu)鲁冯。

并行執(zhí)行8次的執(zhí)行配置

CUDA將核函數(shù)所定義的運(yùn)算稱為線程(Thread),多個線程組成一個塊(Block)色查,多個塊組成網(wǎng)格(Grid)薯演。這樣一個grid可以定義成千上萬個線程,也就解決了并行執(zhí)行上萬次操作的問題综慎。例如涣仿,把前面的程序改為并行執(zhí)行8次:可以用2個block勤庐,每個block中有4個thread示惊。原來的代碼可以改為gpu_print[2, 4](),其中方括號中第一個數(shù)字表示整個grid有多少個block愉镰,方括號中第二個數(shù)字表示一個block有多少個thread米罚。

實(shí)際上,線程(thread)是一個編程上的軟件概念丈探。從硬件來看录择,thread運(yùn)行在一個CUDA核心上,多個thread組成的block運(yùn)行在Streaming Multiprocessor(SM的概念詳見本系列第一篇文章)碗降,多個block組成的grid運(yùn)行在一個GPU顯卡上隘竭。

軟硬件對應(yīng)關(guān)系

CUDA提供了一系列內(nèi)置變量,以記錄thread和block的大小及索引下標(biāo)讼渊。以[2, 4]這樣的配置為例:blockDim.x變量表示block的大小是4动看,即每個block有4個thread,threadIdx.x變量是一個從0到blockDim.x - 1(4-1=3)的索引下標(biāo)爪幻,記錄這是第幾個thread菱皆;gridDim.x變量表示grid的大小是2,即每個grid有2個block挨稿,blockIdx.x變量是一個從0到gridDim.x - 1(2-1=1)的索引下標(biāo)仇轻,記錄這是第幾個block。

CUDA內(nèi)置變量示意圖

某個thread在整個grid中的位置編號為:threadIdx.x + blockIdx.x * blockDim.x奶甘。

使用內(nèi)置變量計算某個thread編號

利用上述變量篷店,我們可以把之前的代碼豐富一下:

fromnumbaimportcudadefcpu_print(N):foriinrange(0,N):print(i)@cuda.jitdefgpu_print(N):idx=cuda.threadIdx.x+cuda.blockIdx.x*cuda.blockDim.xif(idx<N):print(idx)defmain():print("gpu print:")gpu_print[2,4](8)cuda.synchronize()print("cpu print:")cpu_print(8)if__name__=="__main__":main()

執(zhí)行結(jié)果為:

gpu print:

0

1

2

3

4

5

6

7

cpu print:

0

1

2

3

4

5

6

7

這里的GPU函數(shù)在每個CUDA thread中打印了當(dāng)前thread的編號,起到了CPU函數(shù)for循環(huán)同樣的作用臭家。因?yàn)閒or循環(huán)中的計算內(nèi)容互相不依賴疲陕,也就是說吭产,某次循環(huán)只是專心做自己的事情,循環(huán)第i次不影響循環(huán)第j次的計算鸭轮,所以這樣互相不依賴的for循環(huán)非常適合放到CUDA thread里做并行計算臣淤。在實(shí)際使用中,我們一般將CPU代碼中互相不依賴的的for循環(huán)適當(dāng)替換成CUDA代碼窃爷。

這份代碼打印了8個數(shù)字邑蒋,核函數(shù)有一個參數(shù)N,N = 8按厘,假如我們只想打印5個數(shù)字呢医吊?當(dāng)前的執(zhí)行配置共2 * 4 = 8個線程,線程數(shù)8與要執(zhí)行的次數(shù)5不匹配逮京,不過我們已經(jīng)在代碼里寫好了if (idx < N)的判斷語句卿堂,判斷會幫我們過濾不需要的計算。我們只需要把N = 5傳遞給gpu_print函數(shù)中就好懒棉,CUDA仍然會啟動8個thread草描,但是大于等于N的thread不進(jìn)行計算。注意策严,當(dāng)線程數(shù)與計算次數(shù)不一致時穗慕,一定要使用這樣的判斷語句,以保證某個線程的計算不會影響其他線程的數(shù)據(jù)妻导。

線程數(shù)與計算次數(shù)不匹配

Block大小設(shè)置

不同的執(zhí)行配置會影響GPU程序的速度逛绵,一般需要多次調(diào)試才能找到較好的執(zhí)行配置,在實(shí)際編程中倔韭,執(zhí)行配置[gridDim, blockDim]應(yīng)參考下面的方法:

block運(yùn)行在SM上术浪,不同硬件架構(gòu)(Turing、Volta寿酌、Pascal...)的CUDA核心數(shù)不同胰苏,一般需要根據(jù)當(dāng)前硬件來設(shè)置block的大小blockDim(執(zhí)行配置中第二個參數(shù))。一個block中的thread數(shù)最好是32份名、128碟联、256的倍數(shù)。==注意僵腺,限于當(dāng)前硬件的設(shè)計鲤孵,block大小不能超過1024。==

grid的大小gridDim(執(zhí)行配置中第一個參數(shù))辰如,即一個grid中block的個數(shù)可以由總次數(shù)N除以blockDim普监,并向上取整。

例如,我們想并行啟動1000個thread凯正,可以將blockDim設(shè)置為128毙玻,1000 ÷ 128 = 7.8,向上取整為8廊散。使用時桑滩,執(zhí)行配置可以寫成gpuWork[8, 128](),CUDA共啟動8 * 128 = 1024個thread允睹,實(shí)際計算時只使用前1000個thread运准,多余的24個thread不進(jìn)行計算。

注意缭受,這幾個變量比較容易混淆胁澳,再次明確一下:blockDim是block中thread的個數(shù),一個block中的threadIdx最大不超過blockDim米者;gridDim是grid中block的個數(shù)韭畸,一個grid中的blockIdx最大不超過gridDim。

以上討論中蔓搞,block和grid大小均是一維胰丁,實(shí)際編程使用的執(zhí)行配置常常更復(fù)雜,block和grid的大小可以設(shè)置為二維甚至三維败明,如下圖所示隘马。這部分內(nèi)容將在下篇文章中討論太防。

Thread Block Grid

內(nèi)存分配

前文提到妻顶,GPU計算時直接從顯存中讀取數(shù)據(jù),因此每當(dāng)計算時要將數(shù)據(jù)從主存拷貝到顯存上蜒车,用CUDA的術(shù)語來說就是要把數(shù)據(jù)從主機(jī)端拷貝到設(shè)備端讳嘱。CUDA強(qiáng)大之處在于它能自動將數(shù)據(jù)從主機(jī)和設(shè)備間相互拷貝,不需要程序員在代碼中寫明酿愧。這種方法對編程者來說非常方便沥潭,不必對原有的CPU代碼做大量改動。

我們以一個向量加法為例嬉挡,編寫一個向量加法的核函數(shù)如下:

@cuda.jitdefgpu_add(a,b,result,n):# a, b為輸入向量钝鸽,result為輸出向量# 所有向量都是n維# 得到當(dāng)前thread的索引idx=cuda.threadIdx.x+cuda.blockDim.x*cuda.blockIdx.xifidx<n:result[idx]=a[idx]+b[idx]

初始化兩個2千萬維的向量,作為參數(shù)傳遞給核函數(shù):

n=20000000x=np.arange(n).astype(np.int32)y=2*xgpu_result=np.zeros(n)# CUDA執(zhí)行配置threads_per_block=1024blocks_per_grid=math.ceil(n/threads_per_block)gpu_add[blocks_per_grid,threads_per_block](x,y,gpu_result,n)

把上述代碼整合起來庞钢,與CPU代碼做對比拔恰,并驗(yàn)證結(jié)果正確性:

fromnumbaimportcudaimportnumpyasnpimportmathfromtimeimporttime@cuda.jitdefgpu_add(a,b,result,n):idx=cuda.threadIdx.x+cuda.blockDim.x*cuda.blockIdx.xifidx<n:result[idx]=a[idx]+b[idx]defmain():n=20000000x=np.arange(n).astype(np.int32)y=2*xgpu_result=np.zeros(n)cpu_result=np.zeros(n)threads_per_block=1024blocks_per_grid=math.ceil(n/threads_per_block)start=time()gpu_add[blocks_per_grid,threads_per_block](x,y,gpu_result,n)cuda.synchronize()print("gpu vector add time "+str(time()-start))start=time()cpu_result=np.add(x,y)print("cpu vector add time "+str(time()-start))if(np.array_equal(cpu_result,gpu_result)):print("result correct")if__name__=="__main__":main()

運(yùn)行結(jié)果,GPU代碼竟然比CPU代碼慢10+倍基括!

gpu vector add time 13.589356184005737

cpu vector add time 1.2823548316955566

result correct

說好的GPU比CPU快幾十倍上百倍呢颜懊?這里GPU比CPU慢很多原因主要在于:

向量加法的這個計算比較簡單,CPU的numpy已經(jīng)優(yōu)化到了極致,無法突出GPU的優(yōu)勢河爹,我們要解決實(shí)際問題往往比這個復(fù)雜得多匠璧,當(dāng)解決復(fù)雜問題時,優(yōu)化后的GPU代碼將遠(yuǎn)快于CPU代碼咸这。

這份代碼使用CUDA默認(rèn)的統(tǒng)一內(nèi)存管理機(jī)制夷恍,沒有對數(shù)據(jù)的拷貝做優(yōu)化。CUDA的統(tǒng)一內(nèi)存系統(tǒng)是當(dāng)GPU運(yùn)行到某塊數(shù)據(jù)發(fā)現(xiàn)不在設(shè)備端時媳维,再去主機(jī)端中將數(shù)據(jù)拷貝過來裁厅,當(dāng)執(zhí)行完核函數(shù)后,又將所有的內(nèi)存拷貝回主存侨艾。在上面的代碼中执虹,輸入的兩個向量是只讀的,沒必要再拷貝回主存唠梨。

這份代碼沒有做流水線優(yōu)化袋励。CUDA并非同時計算2千萬個數(shù)據(jù),一般分批流水線工作:一邊對2000萬中的某批數(shù)據(jù)進(jìn)行計算当叭,一邊將下一批數(shù)據(jù)從主存拷貝過來茬故。計算占用的是CUDA核心,數(shù)據(jù)拷貝占用的是總線蚁鳖,所需資源不同磺芭,互相不存在競爭關(guān)系。這種機(jī)制被稱為流水線醉箕。這部分內(nèi)容將在下篇文章中討論钾腺。

原因2中本該程序員動腦思考的問題交給了CUDA解決,增加了時間開銷讥裤,所以CUDA非常方便的統(tǒng)一內(nèi)存模型缺點(diǎn)是計算速度慢放棒。針對原因2,我們可以繼續(xù)優(yōu)化這個程序己英,告知GPU哪些數(shù)據(jù)需要拷貝到設(shè)備间螟,哪些需要拷貝回主機(jī)。

fromnumbaimportcudaimportnumpyasnpimportmathfromtimeimporttime@cuda.jitdefgpu_add(a,b,result,n):idx=cuda.threadIdx.x+cuda.blockDim.x*cuda.blockIdx.xifidx<n:result[idx]=a[idx]+b[idx]defmain():n=20000000x=np.arange(n).astype(np.int32)y=2*x# 拷貝數(shù)據(jù)到設(shè)備端x_device=cuda.to_device(x)y_device=cuda.to_device(y)# 在顯卡設(shè)備上初始化一塊用于存放GPU計算結(jié)果的空間gpu_result=cuda.device_array(n)cpu_result=np.empty(n)threads_per_block=1024blocks_per_grid=math.ceil(n/threads_per_block)start=time()gpu_add[blocks_per_grid,threads_per_block](x_device,y_device,gpu_result,n)cuda.synchronize()print("gpu vector add time "+str(time()-start))start=time()cpu_result=np.add(x,y)print("cpu vector add time "+str(time()-start))if(np.array_equal(cpu_result,gpu_result.copy_to_host())):print("result correct!")if__name__=="__main__":main()

這段代碼的運(yùn)行結(jié)果為:

gpu vector add time 0.19940638542175293

cpu vector add time 1.132070541381836

result correct!

至此损肛,可以看到GPU速度終于比CPU快了很多厢破。

Numba對Numpy的比較友好,編程中一定要使用Numpy的數(shù)據(jù)類型治拿。用到的比較多的內(nèi)存分配函數(shù)有:

cuda.device_array(): 在設(shè)備上分配一個空向量摩泪,類似于numpy.empty()

cuda.to_device():將主機(jī)的數(shù)據(jù)拷貝到設(shè)備

ary=np.arange(10)device_ary=cuda.to_device(ary)

cuda.copy_to_host():將設(shè)備的數(shù)據(jù)拷貝回主機(jī)

host_ary=device_ary.copy_to_host()

總結(jié)

Python Numba庫可以調(diào)用CUDA進(jìn)行GPU編程,CPU端被稱為主機(jī)忍啤,GPU端被稱為設(shè)備加勤,運(yùn)行在GPU上的函數(shù)被稱為核函數(shù)仙辟,調(diào)用核函數(shù)時需要有執(zhí)行配置,以告知CUDA以多大的并行粒度來計算鳄梅。使用GPU編程時要合理地將數(shù)據(jù)在主機(jī)和設(shè)備間互相拷貝叠国。

GPU程序執(zhí)行流程

CUDA編程的基本流程為:

初始化,并將必要的數(shù)據(jù)拷貝到GPU設(shè)備的顯存上戴尸。

使用某個執(zhí)行配置粟焊,以一定的并行粒度調(diào)用CUDA核函數(shù)。

CPU和GPU異步計算孙蒙。

將GPU計算結(jié)果拷貝回主機(jī)项棠。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挎峦,隨后出現(xiàn)的幾起案子香追,更是在濱河造成了極大的恐慌,老刑警劉巖坦胶,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件透典,死亡現(xiàn)場離奇詭異,居然都是意外死亡顿苇,警方通過查閱死者的電腦和手機(jī)峭咒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纪岁,“玉大人凑队,你說我怎么就攤上這事♂:玻” “怎么了漩氨?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長导匣。 經(jīng)常有香客問我才菠,道長,這世上最難降的妖魔是什么贡定? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮可都,結(jié)果婚禮上缓待,老公的妹妹穿的比我還像新娘。我一直安慰自己渠牲,他們只是感情好旋炒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著签杈,像睡著了一般瘫镇。 火紅的嫁衣襯著肌膚如雪鼎兽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天铣除,我揣著相機(jī)與錄音谚咬,去河邊找鬼。 笑死尚粘,一個胖子當(dāng)著我的面吹牛择卦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播郎嫁,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秉继,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泽铛?” 一聲冷哼從身側(cè)響起尚辑,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盔腔,沒想到半個月后腌巾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铲觉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年澈蝙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撵幽。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡灯荧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盐杂,到底是詐尸還是另有隱情逗载,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布链烈,位于F島的核電站厉斟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏强衡。R本人自食惡果不足惜擦秽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漩勤。 院中可真熱鬧感挥,春花似錦、人聲如沸越败。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽究飞。三九已至置谦,卻和暖如春堂鲤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媒峡。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工瘟栖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丝蹭。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓慢宗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奔穿。 傳聞我的和親對象是個殘疾皇子镜沽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容