编译原理实验:
要求给定一个特定的功能,分别使用 C/C++、Java、Python 和 Haskell 实现该功能,对采用这几种语言实现的编程效率,程序的规模,程序的运行效率进行对比分析。例如分别使用上述几种语言实现一个简单的矩阵乘法程序,输入两个矩阵,输出一个矩阵,并分析相应的执行效果。
代码其实并不难,C,Java,Python一抓一大把,即便是x86和Haskell,网上也有很多代码可以借鉴,都是程式化的东西,大家写的都差不多。
难点在于x86汇编和haskell的环境配置。我的建议是,用linux系统跑,不然haskell会折磨的你很痛苦,我光是配环境就配了一天多,而舍友用Linux很快就搞定了。
测试不同的数据规模有两种思路:
比如,对50个数据快排10000次。我就是采用这种重复的方法测试的,效果也不错,写起来比生成数据容易,毕竟x86这东西文件读写很麻烦,反之重复就用一个loop就可以。
x86汇编的难度在于环境搭建。最开始自然是建立一个空项目,然后配置masm32的项目依赖。到此时,你自己写一个hello world就可以成功。
但是后面网上x86的代码常常会用到irvine32.inc,尤其是要统计程序时间的时候,需要用到的一个函数就是irvine32.inc里的。
所以首先你要获取到irvine32.inc,把他放到masm32的include文件夹里。
irvine32.inc获取
其次,irvine32.inc本质上就是个头文件,如果你还有自己写的头文件(比如下面这个网站的代码),就可能出现冲突,报错就是can not resolve,此时把自己写的头文件里和irvine32头文件里声明冲突的部分就好。
这个是参考。
汇编排序
在实际应用过程中,导入irvine32后会出现头文件冲突,因此我修改了head.inc。同时,因为我是要反复快排的,所以我还增加了初始化的代码,我的初始化原则是逆序,比如50个数据就是50,49,······,2,1。而快排的结果是正序,所以整个过程就是在反复的初始化逆序,排序到正序。
到这里,汇编就可以成功运行快排并且实现运行时间统计的功能。
main.asm,主文件
TITLE It is Main function
.386
.MODEL flat,stdcall
Include Irvine32.inc
INCLUDE head.inc
Includelib Irvine32.lib
Includelib kernel32.lib
Includelib user32.lib
.data
pArray SDWORD 50 dup(?)
len SDWORD ($-pArray)/4
startTime DWORD ?
endTime DWORD ?
.code
start:
;------------------------------------------------
;初始化
INVOKE Init,ADDR pArray,len
mov EAX,OFFSET pArray
mov ECX,len
P1:
INVOKE PrintNumber,[EAX]
ADD EAX,4
LOOP P1
;测速
INVOKE GetMseconds
mov startTime,eax ;初始时间
mov ecx,10000 ;循环核心代码次数
kernel:
;恢复初始逆序状态
INVOKE Init,ADDR pArray,len
;排序到顺序
INVOKE QuickSort,ADDR pArray,len
loop kernel
INVOKE GetMseconds ;结尾时间
mov endTime,eax
;输出结果
;------------------------------------------------
mov EAX,OFFSET pArray
mov ECX,len
P2:
INVOKE PrintNumber,[EAX]
ADD EAX,4
LOOP P2
mov eax,endTime
sub eax,startTime
INVOKE writeint
INVOKE ExitProcess,0
end start
head.inc,这是头文件,声明了函数原型
ExitProcess PROTO, dwExitCode:DWORD ;Windows API
GetStdHandle PROTO, nStdHandle:DWORD ;Windows API
PrintNumber PROTO, inputNumber:SDWORD
BubbleSort PROTO, pArray:PTR SDWORD,
len : SDWORD
HeapSort PROTO, pArray : PTR SDWORD,
len : SDWORD
AdjustHeap PROTO, pArray : PTR SDWORD,
len : SDWORD,
rootIndex : SDWORD
Init PROTO, pArray : PTR SDWORD,
len : SDWORD
QuickSort PROTO, pArray : PTR SDWORD,
len : SDWORD
QuickSortRecursion PROTO,pArray:PTR SDWORD,
headIndex : SDWORD,
tailIndex : SDWORD
PrintNumber.asm,一个文件,里面实现了数字打印的函数。
TITLE Print Number
.386
.MODEL flat,stdcall
INCLUDE head.inc
.code
PrintNumber PROC USES eax ecx edx esi edi,
inputNumber:SDWORD
LOCAL IsNegative:DWORD,
outputBuffer[13]:BYTE,
handle:DWORD,
charWritten:DWORD
;Decide if inputNumber is a negative number,
;if it is, then IsNegative=1,inputNumber=-inputNumber
mov IsNegative,0
bt inputNumber,31
jnc L1
mov IsNegative,1
neg inputNumber
L1:
mov EAX,inputNumber
mov EDX,0
;EDI will be the dividend.
mov EDI,10
lea ESI,outputBuffer
;"line break" is insert at the end of outputBuffer
mov [ESI+11],BYTE PTR 0Ah
mov [ESI+12],BYTE PTR 0Dh
;ECX is the current index of outputBuffer
mov ECX,11
;convert inputNumber to chars and store it into outputBuffer.
L2:
div edi
add EDX,'0'
dec ECX
mov [ESI+ECX],DL
mov EDX,0
cmp EAX,0
jnz L2
;if inputNumber is negative number, insert '-' at front of string
cmp IsNegative,0
jz L3
dec ECX
mov [ESI+ECX],BYTE PTR '-'
;print the number
L3:
INVOKE GetStdHandle,-11
mov handle,eax
lea EDX,charWritten
;ESI+ECX is the beginning of printed buffer.
add ESI,ECX
;len of printed buffer = 13-ECX
sub ECX,13
neg ECX
INVOKE WriteConsoleA,handle,ESI,ECX,EDX,0
ret
PrintNumber ENDP
END
QuickSort.asm,实现了初始化和快排。
TITLE Quick Sort
.386
.MODEL flat,stdcall
INCLUDE Head.inc
.code
Init PROC USES eax ecx esi,
pArray:PTR SDWORD,
len:SDWORD
mov ecx,len
mov eax,pArray
mov esi,0
L1:
mov [eax+4*esi],ecx
inc esi ;指针右移
loop L1
ret
Init ENDP
QuickSort PROC USES eax ,
pArray:PTR SDWORD,
len : SDWORD
mov eax,len
dec eax
INVOKE QuickSortRecursion,pArray,0,eax
ret
QuickSort ENDP
QuickSortRecursion PROC USES eax ebx ecx esi edi,
pArray : PTR SDWORD,
headIndex : SDWORD,
tailIndex : SDWORD
mov EAX,pArray
mov ESI,headIndex
mov EDI,tailIndex
mov EBX,[EAX+4*ESI]
;ESI = left pointer, EDI = right pointer, EBX= middle number
L1:
CMP ESI,EDI
jge L4
L2:
cmp ESI,EDI
jge L4
cmp EBX,[EAX+4*EDI]
jg E1
dec EDI
jmp L2
E1:
mov ECX,[EAX+4*EDI]
mov [EAX+4*ESI],ECX
inc ESI
L3:
cmp ESI,EDI
jge L4
cmp EBX,[EAX+4*ESI]
jl E2
inc ESI
jmp L3
E2:
mov ECX,[EAX+4*ESI]
mov [EAX+4*EDI],ECX
dec EDI
jmp L1
L4:
mov [EAX+4*ESI],EBX
dec ESI
inc EDI
cmp ESI,headIndex
jle L5
INVOKE QuickSortRecursion,pArray,headIndex,ESI
L5:
cmp EDI,tailIndex
jge END1
INVOKE QuickSortRecursion,pArray,EDI,tailIndex
END1:
ret
QuickSortRecursion ENDP
END
这几个简单的很,都是一刀流。
C
#include
#include
#define LEN 50
#define REPEAT 100
void QuickSort1(int* a, int left, int right);
void init(int* p ,int len);
void print(int *p,int len);
int data[LEN];
int main(void)
{
clock_t start,end;
//初始化
init(data,LEN);
print(data,LEN);
start=clock();
//循环计算
for(int i=0;i<REPEAT;i++)
{
init(data,LEN);
QuickSort1(data,0,LEN-1);
}
end=clock();
//结果
print(data,LEN);
printf("耗时:%f\n",((double)end-start)/CLOCKS_PER_SEC);
}
void print(int *p,int len)
{
for(int i=0;i<len;i++)
{
printf("%d\n",p[i]);
}
}
void init(int* p,int len)
{
for(int i=0;i<len;i++)
{
p[i]=len-i;
}
}
void QuickSort1(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int begin = left, end = right;
//三数取中
//int midIndex = GetThreeMid(a,begin,end);
//Swap(&a[begin],&a[midIndex]);
int pivot = begin;
int key = a[begin];
while (begin < end)
{
//右边找小的,如果不是小于key,继续
while (begin < end && a[end] >= key)
{
end--;
}
//找到比key小的,把它放在坑里,换新坑
a[pivot] = a[end];
pivot = end;
//左边找大的,如果不是大于key,继续
while (begin < end && a[begin] <= key)
{
begin++;
}
//找到比key大的,把它放在坑里,换新坑
a[pivot] = a[begin];
pivot = begin;
}
a[pivot] = key;//bengin 与 end 相遇,相遇的位置一定是一个坑
QuickSort1(a, left, pivot - 1);
QuickSort1(a, pivot + 1, right);
}
Java
public class Main {
public static void main(String[] args) {
int LEN=50;
int[] array = new int[LEN];
init(array,LEN);
print(array,LEN);
long start = System.currentTimeMillis();
for(int i=0;i<100;i++){
init(array,LEN);
sort(array,0,LEN-1);
}
long end = System.currentTimeMillis();
print(array,LEN);
double cost = (end - start) / 100.0; // 单位转换为秒
System.out.println("cost time = " + cost + "s");
}
public static void print(int[] arrays,int len){
for(int i=0;i<len;i++){
System.out.println(arrays[i]);
}
}
public static void init(int[] arrays,int len){
for(int i=0;i<len;i++){
arrays[i]=len-i;
}
}
public static void sort(int[] arrays, int left, int right) {
if(left > right) {
return;
}
int l = left;
int r = right;
int pivot = arrays[left];
int temp = 0;
while(l < r) {
while(pivot <= arrays[r] && l < r) {
r--;
}
while(pivot >= arrays[l] && l < r) {
l++;
}
if(l <= r) {
temp = arrays[r];
arrays[r] = arrays[l];
arrays[l] = temp;
}
}
arrays[left] = arrays[l];
arrays[l] = pivot;
sort(arrays, left, l - 1);
sort(arrays, l + 1, right);
}
}
Python
import time
def Qsort(arr):
if len(arr) > 1:
pivot = arr[len(arr) - 1]
l, h = [], []
arr.remove(pivot)
for ele in arr:
if ele >= pivot:
h.append(ele)
else:
l.append(ele)
return Qsort(l) + [pivot] + Qsort(h)
else:
return arr
res = []
times = 100
data_scale=50
start = time.time()
for i in range(times):
test_arr = list(range(data_scale))[::-1] # 逆序
res = Qsort(test_arr)
end = time.time()
print(res)
print(end-start)
Haskell在windows下很难配置,再说一次,建议你去Linux,已经写好的代码在Linux上都可以轻松复现,也不用担心前功尽弃。我就这么说吧,前面4种语言+报告,顶多一天也就搞定了,而你要是在Windows上卡一整天,那就真的是亏死,时间翻倍。
Stack官方介绍
haskell是一门语言,对应很多工具。其中,stack是一个比较好的平台,可以方便的下载编译器,环境依赖。
听起来不错,但是呢,学了这么久编程,像haskell这么多bug的东西我还是头一次见到。其安装过程究极麻烦,而且容易出现各种问题,我就踩了好多坑,在这里我给出参考文章,并且指出可能出现问题的地方:
【VS Code】Windows10下VS Code配置Haskell语言环境
代码不放出来了,后面去折腾linux。
我武断地将 haskell 里 bug 多的问题归结于生态差。其实 haskell 生态不差,Haskell 真正的问题是 windows 兼容性差,容易出错。后面又去 linux 上试了一下,很快就通了,由此可见,Windows 或许不太适合程序员这个职业,他更多的是提供一个窗口界面,供普通用户或者程序员日常使用,真要工作起来,为了避免各种不兼容,还是应该用 linux。