- UID
- 3808
- 精华
- 积分
- 1478
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
本帖最后由 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
Win32ASM:本程序在获取文件属性上得到了群大佬GodOfHack与QQ小冰GUID666.....的帮助,表示感谢!
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ; 文件映射 FILE MAPPING
- ; 程序来自:[url]www.0xAA55.com[/url]
- ; 该程序遵循较为宽松的MIT许可
- ; Copyright @ watermelon/ZXG/trace-shadow
- ; 如果有什么不足的地方请联系我:[email]starlight_chou@163.com[/email]
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- .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小冰[GUID:6666...]的指导,小弟在此表示感谢。
- _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 |
|