【C Python Win32ASM】编码问题与速度对比
本帖最后由 watermelon 于 2020-4-4 16:27 编辑最近老师对我的Python代码中对try/except来判断文件编码的操作产生了疑问,问我这个是什么意思?所以要求我写一个文档来解释一下并且对比一下python和C的速度问题。
1. python编码问题
1.1文件读写方式;
一般来说,文件读写有两种常见的方式;
一种是硬盘I/O,一种是文件的内存映射。
其中,文件的硬盘读写是最为常见的文件读写方式,一般程序语言的默认教程中举例均为文件的硬盘读写方式,其优点是编写简单,考虑的因素比较少,缺点是速度太慢,当读写大体积文件的时候容易使程序卡死而进入假死状态
(无响应)。
第二种文件的内存映射(file mapping)是文件读写的一种高级方式,其不再使用硬盘进而使用内存来进行文件数据的交换。将硬盘上文件的数据映射到内存中,由于内存的数据传输速度比硬盘的数据传输速度要高很多,所以文件的内存映射会非常的快,处理大数据文件不需要考虑速度问题,但是需要考虑其他的一些问题:如Windows系统上的内存分页,内存分配机制的问题。内存映射的不足之处在于文件映射无法创建新文件(即无法映射空文件);同时文件映射只能修改,编写与源文件中数据长度相等的字符串,所以在添加与源文件长度不相符的内容的时候,应该应用常规的硬盘文件读写方式;最后要注意的一点是文件映射读写的数据是二进制格式的数据,需要对其编码进行判断,转换成我们熟知的常规字符串才可以字符串操作。
1.2 二进制数据与文本数据;
二进制数据是由二进制字符串组成,如利用记事本打开一个exe文件,会看到由于二进制文件与文本文件互不相通的问题而产生的乱码,此时需要用专门的二进制文件查看工具(如WinHex)来查看二进制文件。
文本数据是我们常见的可以用记事本打开的文件数据,其字符串是常见的“abcdefg”这类的我们读得懂的字符来显示的。
总体来说,二进制文件用于计算机的读写,文本文件用于方便人类的读写。
1.3 文件的编码问题
由于二进制文件与文本文件不是一个类型的文件,所以需要一种转换模式可以帮助二进制文件与文本文件之间自由的转换,而这个转换的规则就是文件的编码。一般来说,我们可能会见到过ASCII格式、UTF-8格式、GBK格式、GB2312格式等类型的编码。
文本文件给计算机传递信息,计算机只知道01010101二进制,所以需要将我们的abcedf编码(encode)成0101010给计算机认识;当我们想知道计算机干什么的时候,由于计算机是01010101,所以需要将00101010101的二进制数据解码(decode)成常见的字符串abcdef来给我们读写。
由于python的文件映射出来的数据为二进制,并且如果编码/解码格式选取不正确可能会引发异常,所以可能需要异常处理结构来处理编码的问题,这种情况需要具体问题具体分析。
2.CPython与C在文件映射读取方面的速度比较;
经过编写代码与实验,在循环100次文件映射读取的基础上,CPython代码的耗时为0.024秒左右;使用Windows API编写的在Windows平台上的C语言程序耗时基本为0.054秒,使用Linux上的文件映射mmap编写的程序在编译命令gcc filemap.c –o filemap.o下编译完成后,程序耗时约为0.005秒(比python快了将近5倍)。
实验的程序:
Cpython代码:
# coding:utf-8
# Purpose: Try to compare the speed of C and CPython code.
import time
import mmap
import os
import os.path
# Get the file size,
# if file is empty, then it can not be file-mapped.
def CheckFileEmpty(filename):
return os.path.getsize(filename)
def MapFile(filename):
with open(filename, 'rb') as f:
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
bytes_content = mm.read()
mm.close()
# Decode the bytes string.
try:
content = bytes_content.decode('utf-8')
except:
content = bytes_content.decode('GB2312')
# Output the file content.
#print(content)
def main():
# Start time.
t1 = time.time()
# Begin the 100 cycles.
for i in range(100):
# Check the file size.
if CheckFileEmpty('temp.txt') == 0:
print('The file size if zero, please choose another file.')
return
# Begin to file mapping.
MapFile('temp.txt')
t2 = time.time()
print('Time consuming: ', str(t2-t1)[:9], 's')
if __name__ == '__main__':
main()
Linux上C语言:
// This program's time cosumption is 0.005s
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <sys/time.h>
#ifndef BUF_SIZE
#define BUF_SIZE 4096
#endif
void FileMap(const char *filename)
{
// File descriptor.
int fp;
// Set the file attributes structure.
struct stat st;
//Open the file.
fp = open(filename, O_RDWR);
if(fp == -1)
{
printf("[ * ] Can't open the file...\n");
printf("[ * ] Maybe the file access is denied...\n");
return ;
}
// Check the file size.
fstat(fp, &st);
if(st.st_size == 0)
{
printf("[ * ] Can't make the empty file to the file mapping...\n");
goto label2;
}
// Set the pointer of mmap return,
// which is also the address of the file content.
char *buffer = (char*)malloc(sizeof(char)*BUF_SIZE);
// Record the buffer address.
// because, when use the mmap, buffer address has been changed.
char *ptr = buffer;
if(buffer == NULL)
{
printf("[ * ] Memory allocate is failed...\n");
goto label2;
}
buffer = mmap(NULL, BUF_SIZE, PROT_READ, MAP_SHARED, fp, 0);
if(buffer == NULL)
{
printf("[ * ] File mapping has error...\n");
goto label1;
}
//printf("[ * ]The file content is :\n%s\n", buffer);
label1:
free(ptr);
label2:
close(fp);
//printf("[ * ] All done!\n");
return ;
}
int main(int argc, char *argv[])
{
struct timeval starttime;
struct timeval endtime;
double timeuse = 0;
gettimeofday(&starttime, NULL);
// 100 cycles;
for(int i = 0;i<100;i++)
FileMap("hello.txt");
gettimeofday(&endtime, NULL);
timeuse = 1000000 * (endtime.tv_sec - starttime.tv_sec) +
endtime.tv_usec - starttime.tv_usec;
timeuse = timeuse / 1000000;
printf("Time use=%lfs\n", timeuse);
return 0;
}
利用Windows API 编写的文件映射读取的C语言程序:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#ifndef BUF_SIZE
#define BUF_SIZE 0 // Used to map the whole file.
#endif
// The file mapping can not manipulate the empty file,
// so we should check the file is empty or not.
DWORD CheckFileEmpty(CHAR *filename)
{
// Initialize the file attribute information structure.
WIN32_FILE_ATTRIBUTE_DATA ws32 = { 0 };
BOOL status = GetFileAttributesExA(filename,
GetFileExInfoStandard,
(LPVOID)&ws32);
if (status == FALSE)
{
printf("GetFileAttributesExA has a error, the code is: %d\n", GetLastError());
return 0;
}
// Return the file size.
// Because the file size is very small,
// so, return the low part is OK.
return ws32.nFileSizeLow;
}
// Use the file mapping to get the content of target file.
VOID FileMapping(CHAR *filename)
{
// Get the file open handle.
HANDLE hFile = CreateFileA(filename,
GENERIC_READ | GENERIC_WRITE,
0, // Exclusive this process to manipulate the file.
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFileA has error, the code is: %d\n", GetLastError());
return;
}
// Create a file mapping object.
HANDLE hFileMap = CreateFileMappingA(hFile,
NULL,
PAGE_READWRITE, // We can read and write to this file map view.
0,
BUF_SIZE, // Get the BUF_SIZE length of data.
filename);
if (hFileMap == INVALID_HANDLE_VALUE)
{
printf("CreateFileMappingA has error, the code is:%d\n", GetLastError());
goto label2;
}
// Begin to get the content of this file mapping.
LPVOID lpFileMap = MapViewOfFile(hFileMap,
FILE_MAP_READ,
0,
0,
// This parameter should be the same with CreateFileMappingA
// which has the specific data length.
BUF_SIZE);
if (lpFileMap == NULL)
{
printf("MapViewOfFile has error, the code is:%d\n", GetLastError());
goto label1;
}
//printf("The file content:%s\n", lpFileMap);
label1:
CloseHandle(hFileMap);
//printf("Close handle: hFileMap.\n");
label2:
CloseHandle(hFile);
//printf("Close handle: hFile.\n");
UnmapViewOfFile((LPCVOID)lpFileMap);
//printf("Unmap the file: lpFileMap.\n");
return;
}
int main(int argc, char *argv[])
{
// Begin to count the program running time.
LARGE_INTEGER foreTime = { 0 };
LARGE_INTEGER backTime = { 0 };
LARGE_INTEGER freq = { 0 };
// Used to set the specific threads have connection to
// which CPU cores.
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&foreTime);
// Cycle the program 100 times.
for (int i = 0; i < 100; i++)
{
DWORD dwFileSize = 0;
if ((dwFileSize = CheckFileEmpty("test.txt")) == 0)
{
printf("The empty file can not be file-mapped.\n");
return 0;
}
//printf("The file size is : %d bytes.\n", dwFileSize);
// Begin the file mapping.
FileMapping("test.txt");
}
QueryPerformanceCounter(&backTime);
printf("The time cosumption is : %fs\n",
(FLOAT)(backTime.QuadPart - foreTime.QuadPart) / (FLOAT)freq.QuadPart);
return 0;
}
实验文本:temp.txt/test.txt/hello.txt
Hello world!
Win32ASM:本程序在获取文件属性上得到了群大佬GodOfHack与QQ小冰GUID666.....的帮助,表示感谢!
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 文件映射 FILE MAPPING
; 程序来自:www.0xAA55.com
; 该程序遵循较为宽松的MIT许可
; Copyright @ watermelon/ZXG/trace-shadow
; 如果有什么不足的地方请联系我:starlight_chou@163.com
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.386
.model flat, stdcall
option casemap:none
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; include 文件
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
include windows.inc
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 数据段
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.data
szMsgCaption db 'Information', 0
szFileName db 'hello.txt', 0
szError db 'Error', 0
szErrorCode db 'Error code is: %d', 0
szEmptyFile db 'This is an empty file!', 0
szFormat db '%d μs', 0
; 用于记录程序运行时间的相关变量
dqForeTime dq 0
dqBackTime dq 0
dqFreq dq 0
dqTime dq 0
dwlm dd 1000000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 代码段
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.code
; 判断文件是否为空文件
; 返回值:文件的大小
; 注意:本程序在GetFileAttributesEx中的第二个参数上受到了
; 群大佬GodOfHacker与QQ小冰的指导,小弟在此表示感谢。
_CheckEmptyFile proc uses ecx
LOCAL @ws32:WIN32_FILE_ATTRIBUTE_DATA
LOCAL @errorCode:dword
; 初始化结构体,全部填零。
invoke RtlZeroMemory, addr @ws32, sizeof WIN32_FILE_ATTRIBUTE_DATA
; 获取文件属性
; 这里第二个参数应该填GetFileExInfoStandard
; 由于masm32中没有这个参数
; 但是由于本身这个参数位于枚举类型GET_FILEEX_INFO_LEVELS中
; 定义如下
; typedef enum _GET_FILEEX_INFO_LEVELS {
; GetFileExInfoStandard,
; GetFileExMaxInfoLevel
; } GET_FILEEX_INFO_LEVELS;
; 依据枚举类型中的知识,每一个元素默认从0号开始往后排
; 所以枚举里面的第一个元素为0
invoke GetFileAttributesEx, offset szFileName, \
0, \
addr @ws32
.if eax == FALSE
; 获取last error
call GetLastError
invoke wsprintf, addr @errorCode, offset szErrorCode, eax
invoke MessageBox, NULL, addr @errorCode, offset szError, MB_OK
xor eax, eax
ret
.endif
; 由于此处测试的文件不会大于4G,所以就返回低位的数值了。
mov eax, @ws32.nFileSizeLow
ret
_CheckEmptyFile endp
; 文件映射
; 无返回值
_FileMapping proc uses ecx
LOCAL @hFile:dword
LOCAL @hFileMap:dword
LOCAL @errorCode:dword
; 存储文件映射读取出来的内容的首地址
LOCAL @content_addr:dword
LOCAL @mapViewofFile
; 打开文件
invoke CreateFile, offset szFileName, GENERIC_READ or GENERIC_WRITE, \
0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
mov @hFile, eax
.if eax == INVALID_HANDLE_VALUE
@@:
; 获取last error
call GetLastError
invoke wsprintf, addr @errorCode, offset szErrorCode, eax
invoke MessageBox, NULL, addr @errorCode, offset szError, MB_OK
xor eax, eax
ret
.endif
; 创建一个文件映射对象
invoke CreateFileMapping, @hFile, NULL, \
PAGE_READWRITE, 0, 0, offset szFileName
mov @hFileMap, eax
.if eax == INVALID_HANDLE_VALUE
invoke CloseHandle, @hFile
jmp @B
.endif
; 获取文件内容
invoke MapViewOfFile, @hFileMap, \
FILE_MAP_READ, 0, 0, 0
.if eax == NULL
invoke CloseHandle, @hFile
invoke CloseHandle, @hFileMap
jmp @B
.endif
mov @content_addr, eax
; 显示文件映射的内容
;invoke MessageBox, NULL, @content_addr, offset szMsgCaption, MB_OK
; 关闭句柄
invoke CloseHandle, @hFile
invoke CloseHandle, @hFileMap
invoke UnmapViewOfFile, @mapViewofFile
xor eax, eax
ret
_FileMapping endp
; 计时函数
; 返回值:无
_TimeConsumption proc
; 用于记录输出时间的格式化字符串地址
LOCAL @time_addr:dword
; 开始计时
invoke QueryPerformanceFrequency, offset dqFreq
invoke QueryPerformanceCounter, offset dqForeTime
; 循环100次文件映射的过程
mov ecx, 100
@@:
call _CheckEmptyFile
.if eax == 0
invoke MessageBox, NULL, offset szEmptyFile, offset szMsgCaption, MB_OK
ret
.else
call _FileMapping
.endif
loop @B
; 结束计时
invoke QueryPerformanceCounter, offset dqBackTime
; 处理浮点数,参考罗云彬《Windows环境下32位汇编语言程序设计》
mov eax, dword ptr dqForeTime
mov edx, dword ptr dqForeTime + 4
sub dword ptr dqBackTime, eax
sbb dword ptr dqBackTime + 4, edx
finit
fild dqFreq
fild dqBackTime
fimul dwlm
fdivr
fistp dqTime ; μs
invoke wsprintf, addr @time_addr, offset szFormat, dqTime
invoke MessageBox, NULL, addr @time_addr, offset szMsgCaption, MB_OK
xor eax, eax
ret
_TimeConsumption endp
; 主程序
start:
call _TimeConsumption
invoke ExitProcess, NULL
end start
运行得到的时间和Win32API写的写语言的release版本时间差不多,也是0.05秒左右
小弟对于程序的优化问题不是特别懂,希望各位大佬对本文看到有什么问题后直接留言批评我!
实验环境:
Windows: visual studio2013, 32位程序
Cpython:version 3.8.1 64位
Linux上:GCC 8.3.0
汇编器:MASM32 这个速度比起来意义不大啊。整个费时的比较才有效。 smitest 发表于 2020-4-12 14:05
这个速度比起来意义不大啊。整个费时的比较才有效。
哦哦有道理,本身一开始这个任务和文件映射有关,所以就使用了文件映射这种方法,看来需要再增加循环次数或者改用其他的比较形式。 你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的会比系统API还快? 系统消息 发表于 2020-4-15 09:49
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的 ...
我的那个cpython程序是在windows平台下的,后来我也反思了我的程序,感觉不是单一变量来试验了.... 系统消息 发表于 2020-4-15 09:49
你说CPython映射文件速度的问题是在Windows下还是Linux下?屁眼不最终也是通过调用系统API来实现的吗?真的 ...
你需要注意的是,他每个循环里,都要打开一次文件,映射一下,再关闭文件。而且他在Windows下使用的是缓慢的A系API。这个测试没得什么意义,因为他比的不是通过映射来修改文件的速度,而是“映射的速度”。
就好比我GPU上传、下载纹理资源很慢,但进行图形处理的速度很快,可测速者每个循环都上传、下载纹理,然后只绘制一次,就得出结论“GPU没得CPU快”。 0xAA55 发表于 2020-4-18 03:01
你需要注意的是,他每个循环里,都要打开一次文件,映射一下,再关闭文件。而且他在Windows下使用的是缓 ...
哦哦学到了,如果比文件映射还是要比读写速度;并且原来A系API要慢啊!
页:
[1]