声明:本文已经投稿至“2024年中国高校计算机教育大会”,并发表于微信公众号“于仕琪”,可以转载,但不可修改。
作者:于仕琪,郑锋,廖琪梅,田蕾
单位:南方科技大学计算机科学与工程系
摘要:随着人工智能的兴起,学生对编程的热情逐渐从C/C++向Python迁移,对于计算机硬件体系结构的理解也呈现逐年下降的趋势。当前许多人工智能从业者做的是人工智能算法设计,但参与基础人工智能软件开发的相对较少。我们认为本科生教育中应该加强基础软件开发的教学,可利用学生对人工智能的热情,培养学生开发基础底层软件平台的能力。本文作者在多年教学中,面向人工智能时代的社会需求,将一门传统的程序设计课程“C/C++程序设计”,逐年优化和改进成为一门包含多项内容的“高级计算机程序设计”课程。该课程通过引入开源项目作为案例,向学生传授C和C++的独特优势,介绍多种CPU架构、GPU编程、计算瓶颈分析、各种开发工具和新型Rust语言等内容。这些内容让学生更加深入地理解程序设计,提升了教学质量,课程受到了学生的广泛欢迎。
关键词:程序设计,人工智能,C,C++,Rust
导言
目前人工智能已经成为热门的方向,大量的科研和技术人员投入其中。在科研人员中,大部分人的工作是深度学习算法设计,即利用开源的PyTorch或其他深度学习训练库,设计不同的深度网络结构,并在各种数据集上进行训练和评估。而在人工智能框架开发方面,例如优化计算效率、发挥硬件性能方面只有相对较少的人员投入。从国家需求来讲,我们需要在人工智能软硬件的基础架构有人才和技术积累。
计算机和人工智能创新想法的验证需要程序设计,其中C和C++语言是大部分基础软件的实现语言,有着重要的地位。C和C++编程语言因为语法复杂,很多学生即使进行了系统学习,仍然难以编写出稳定且少错的程序,尤其是其中的指针和内存管理机制,令无数学生困扰不已。随着人工智能的发展,Python编程语言的使用率日益提升,有的学生认为学好Python就足够了。当学生的就业方向是网络开发或者移动应用开发时,会认为熟悉Java已经足够;如果就业方向是网页前端开发,会觉得HTML/JavaScript足够。这样的现状让很多学生缺乏学习C/C++的热情,甚至质疑学习C/C++的必要性。其实不然,从学生的职业规划来讲,深厚的计算机基础可以打破“35岁退休”魔咒。如果只追技术热点而缺少深入理解,学生的职业发展难以达到较高的层次。
1. 目前面临的困难
1.1 学生需要精通多门编程语言
程序设计是计算机和人工智能等相关专业本科生的一项基本能力。随着技术发展和需求的拓宽,学生除了掌握C和C++编程语言之外,还需要掌握Java、Python甚至JavaScript等语言。此外,一些新型的编程语言如Rust也展示了潜力,需要学生有所了解甚至需要掌握。这么多编程语言,在本科生教育中为每种语言开设相应的课程不太现实,因为这会挤压其他课程的课时,无法在培养方案中实施。
当前大部分学校将C语言作为本科教育的第一门编程语言,并辅以C++或Java作为第二门编程语言课,也可能将Python作为选修课程。C和C++作为两门课,因这两门语言的基础语法有较大重合度,会面临内容重复的问题。如果在C++课程中不讲C课程中的基础语法,那么只能讲C++的高级特性,而C++最近20年的技术路线非常激进,引入了大量的新特性,但学生缺乏工程基础,即使学了许多C++新特性,也难以理解为何需要这些新特性,无法将之变成真正的知识并进行应用。
C和C++语言中的指针和内存管理是其精髓和特色,同时也是其难点,有大量的学生无法深入理解并掌握这个知识点。C和C++的语法规则因为历史原因有大量晦涩的知识点,不像Java那样直白简单。例如C和C++中基本数据类型的变量默认不进行初始化,内存管理不善容易泄露或者重复释放,进而导致程序崩溃。这让学生的程序中有大量的BUG,让学生的自信心严重受挫。要深层理解程序的运行,除了理解内存还需要理解CPU缓存和寄存器。行业的发展需要学生精通多门编程语言,但现实却并非如此,大部分学生连一门编程语言都无法精通。
1.2 人工智能时代C/C++的不可替代性
因为学习C和C++语言面临诸多困难,有些学生甚至认为现在进入人工智能时代了,会用Python就足够了,甚至不会编程也可以,因为有ChatGPT这类工具自动生成代码。深度学习的热潮带来Python语言热,Python语言因为易于实现想法,所以广泛应用于深度学习的训练和部署中。如果我们“近距离”看一下人工智能基础软件,便会发现Python主要应用在算法设计和部署中,底层软件如PyTorch和TensorFlow都是用C或C++实现,编译成高效率的Python包供调用。如果底层硬件是英伟达的GPU,那么需要调用CUDA库实现各种算法;如果底层是华为的昇腾GPU,那么需要用华为的Ascend C接口和相关编译器。
使用最广泛的操作系统Linux是使用C语言开发,编译器GCC是C语言,数据库管理系统如MySQL和PostgreSQL是C语言,开源计算机视觉库OpenCV是C++语言。也就是说,目前大部分计算机领域的基础软件是采用C或C++实现。虽然新型的编程语言Rust很有前景,但尚未成广泛生态。在未来的若干年内,C和C++语言在计算机基础软件开发中依然不可替代。我们的人才培养,不仅需要培养人工智能算法设计方面的人才,更需要培养有着深厚计算机基础的系统开发人才。我们无法建立“空中楼阁”,ChatGPT的成功也凸显芯片、通信、计算机体系结构、编译器等基础开发愈发重要。
1.3 教学中的问题
在程序设计课程教学中,课程容易沦为“语法规则课”,课程的目的容易降低为“语法规则学习”。在这种低要求下,培养出的学生只会将简单代码输入IDE(集成开发环境),然后点击“运行”按钮验证结果。学生不理解“编译”“连接”等基本编程概念,也缺乏开发大一点规模软件的经验。程序设计课程中的例子往往比较简单,可以帮助理解语法规则,但学了这些有什么用,必须使用一些真实的案例来回答。受限于高校教师一般缺乏工业界的工作经验,难以举出高质量的真实案例。
现代程序开发不仅涉及语法规则,还会涉及大量的工具软件和不同的开发平台,例如各种不同IDE、各种编译器、Makefile/cmake等代码管理工具、git版本控制和合作工具等。较大比例的同学习惯于使用Windows操作系统的图形界面和笔记本电脑,缺少DIY桌面电脑带来的CPU、内存、磁盘等硬件的直观概念,也无法理解有了易用的图形界面为什么要用命令行窗口输入命令。而计算机行业的现实是大部分开发,包括人工智能基础软件、网络服务器软件乃至手机App都是Linux或类Unix系统。
学生的学习动力也是一项挑战。学生会认为学习Java、Python和JavaScript跟就业市场更匹配,C和C++这类很难的编程语言是否有必要学习?我们在本科生教育中固然要帮助学生打好基础,深厚的基础方能让学生在职业生涯中“以不变应万变”,破解“35岁退休”问题,同时我们也必须考虑到学生的就业需求,让学生熟练使用各种开发工具,快速掌握一些“容易”的编程语言,平滑地进入职场。
2. 课程内容设计
本节介绍作者所在的南方科技大学的程序设计课程设计。南方科技大学本科生第一年不分专业,所有本科生都进行程序设计的通识教育。程序设计通识教育包括Java、C、Pythoy、Matlab多门编程语言,学生根据自己的需求自由选择。如学生有意愿选择计算机专业,则需要修读Java编程语言。计算机专业将Java而非C作为第一门编程语言的原因在于Java的语法规则简单,入门相对容易,可以避免C语言带来的挫败感,重点培养学生的编程逻辑而非对语法细节的掌握,提升学生的编程兴趣。
学生在大学一年级学习的通识课程中的Java编程语言,目的是培养学生的逻辑思维能力,以及基础的编程能力,但这些基础能力无法应对人工智能时代对专业人才的要求。为了全面培养学生的程序设计能力,南科大计算机相关专业本科生的第二门程序设计课为“高级计算机程序设计”课程,面向人工智能时代的需求,经过多年迭代和改进而成。课程的所有课件和例程已经在GitHub网站开源[1],课程视频在哔哩哔哩网站播放超过20万次[2]。课程的上课安排为每星期4课时,其中理论课2课时,实验课2课时。理论课讲授语法知识和编程中的注意事项,实验课通过练习题让学生巩固理论课的知识点。
2.1 C和C++合并教学
鉴于修读本课程的学生已经有Java语言的程序设计基础,本课程将C和C++两门编程语言合并起来一起讲授。C和C++两门语言的语法规则有一定程度的重合,但也有差异。两门语言一起讲授,既可以高效率地讲授相同的部分,避免分成两门课的衔接问题;也可以对两门语言中的差异进行对比,突出两门语言的不同,例如基础数据类型的变量的初始化,两门语言都不进行初始化;C++有函数重载,C语言无函数重载;C和C++虽然都有结构体struct,但二者的结构体是完全不同的。这样的对比学习,有助于学生对知识点掌握的更深刻。
两门语言都涉及指针和内存管理,这是课程的难点,也是重点。如果学生没有掌握指针和内存管理,那不算学过C和C++语言。我们在课程中广泛采用内存示意图和动画对指针和内存这个知识点进行讲解,并将内存管理这个知识点渗透到课程中大部分知识点中。例如C++中类的默认复制构造函数和赋值运算符的重载,都会涉及内存管理问题。课程对各种可能发生的指针和内存管理问题进行讲解和深入分析。
课程内容设计以C和C++语言编程为基本内容,辅以介绍不同的CPU架构、GPU、Rust语言以及各种开发工具,并引入开源软件作为案例,让学生在掌握基本知识点之上,尽可能地扩大知识面。课程的最终目的是加深学生对计算机的理解,提升学生动手能力,并开阔学生的眼界。
2.2 突出C/C++的独特优势
课程设计的另一个目的是让学生体会到C和C++语言的魅力,所以如何提升程序的效率是课程的重点内容之一。课程内容会涉及编译器不同编译选项(如gcc的O3)对程序速度的影响,单指令多数据(SIMD)的用法,OpenMP充分利用CPU多核心等。这些程序优化方法可以让程序有几十倍的速度提升,让学生亲身感受到C和C++语言的价值。
课程将矩阵乘法的实现作为核心例子。矩阵乘法的原理在“线性代数”课程中有讲解,是现在深度学习领域中最核心的计算,无论普通卷积还是Transformer中的Attention模块,都是矩阵乘法计算。矩阵乘法的计算量在大多数深度学习模型中占90%以上的比重。矩阵乘法的基本实现很简单,只需要不到10行代码,但将之优化以提升速度却不容易。学生除了使用编译器优化选项、SIMD和多核并行,还需要考虑充分利用CPU的缓存(Cache),提升缓存读取的命中率等一系列方法。矩阵乘法的例子可以让学生充分感受到程序效率跟计算机体系结构密切相关,再将自己的程序跟跟专业的矩阵计算库(如OpenBLAS)进行对比,学生会深刻感受到自己的程序与专业程序的差距,这有助于提升学生对计算机的理解。
2.3 介绍X86、ARM和RISC-V架构
为了让学生充分理解不同CPU架构的相同和不同之处,课程中介绍了三种最常见CPU架构X86、ARM和RISC-V的异同。课程还提供了ARM开发板和华为的ARM云服务器供学生使用,并建议学生在ARM平台上完成课程项目。通过接触ARM系统,学生可以消除对ARM的陌生感,感受到使用ARM进行开发与在其他CPU架构上开发并无明显区别,消除学生畏难情绪。随着RISC-V硬件的日渐丰富,未来课程也会为学生提供RISC-V的开发环境。
课程还介绍了不同CPU架构中的SIMD指令,并讲解SIMD指令加速程序效率的作用。并鼓励学生使用SIMD指令(X86的AVX2或者ARM的NEON)去加速矩阵乘法的计算。通过使用SIMD,学生不仅可以学会编程技巧,还可以更好的理解CPU中的寄存器和计算机制。
声明:本文原发表于微信公众号“于仕琪”,可以转载,但不可修改。
2.4 介绍CUDA/Ascend C编程
现有的各种人工智能基础软件基本上都是运行在专用的GPU上,要发挥GPU的计算能力则需要专用的第三方库,如英伟达的CUDA和华为的Ascend C。课程中增加了CUDA和Ascend C的入门知识,通过在GPU上进行简单的矩阵运算,让学生了解CUDA和Ascend C的基本用法。
考虑到不是所有的学生都有GPU服务器,学校专门为本课程配置了一台有GPU的服务器,供学生远程登录使用。这部分内容仅是入门知识,目的不是让学生精通GPU编程,而是让学生有所了解并体验硬件加速的能力,看到相关名词不会产生畏惧心理。未来有需要的时候,学生可以快速上手GPU开发,为学生未来的发展提供知识储备。
2.5 介绍Rust编程语言
Rust是一门新型的编程语言,是一门对内存安全的语言。目前Linux内核开发[3]和Windows开发[4]都支持Rust语言。Rust语言未来有替换C和C++语言的潜力,但Rust生态尚未成熟,专门开设一门课讲授Rust未必妥当。鉴于此,本课程在C和C++内容之外,增加了Rust入门知识。相信学生在精通了C和C++之后,通过课堂介绍的Rust入门知识,可以通过自学快速地掌握Rust语言。
2.6 介绍各种开发工具的用法
要形成真正的开发能力,除了掌握编程语言的相关知识,还需要熟练使用各种开发工具。鉴于理论课的内容已经很多,以下工具软件的学习放在课程的实验课中。这些软件的使用跟实验课练习一起考核,计入学生的考核成绩。
1. Linux基本用法和常用命令:建议学生使用Linux系统开发。如果学生的个人电脑是Windows,则建议安装WSL(Windows Subsystem for Linux)。这样学生可以快速了解Linux的使用,特别是常用命令的使用。
2. gcc和g++编译器的用法:主要介绍一些常用选项,如“-c”、“-o”、“-O3”。学生通过在命令行中使用编译器进行编译,强化对“编译”、“连接”和“运行”等概念的认知。
3. Makefile的用法:通过一个包含多个源文件的项目,介绍Makefile的对多文件的管理。学生也可以通过Makefile理解多个源文件之间的关系。
4. cmake的用法:针对跨平台源文件管理,介绍Makefile的不足,引出cmake的必要性。
5. VS Code IDE的用法。介绍IDE将所有开发工具集中在一起的便利性。
6. git的用法:通过代码的频繁修改会覆盖修改历史,进而引出版本管理的重要性,以及多人合作中的版本控制问题。
2.7 将开源项目引入课程
鉴于企业中的真实案例很难公开获得并引入课堂,可以将优秀的开源项目引入课堂,作为教学案例。本课程引入了两个开源项目,OpenCV[5]和OpenBLAS[6]。OpenCV是一个开源计算机视觉库,采用C++语言写成。课程引入OpenCV的代码,展示函数重载、运算符重载、内存管理、类模板等知识点在OpenCV中的实现。这让学生在简单的小例子之外,还能看到真实的大型项目代码,并从中学习。真实案例中也会存在不完美的设计,例如OpenCV中采用int类型标识矩阵的行数和列数,而非更合理的size_t类型,这是OpenCV的一个设计缺陷。通过介绍OpenCV 20多年的历史,来阐述这个缺陷的来源,并阐述软件在长期演进过程中为了兼容老版本,不得不做出的一些妥协,设计很难完美。真实项目如同一个小社会,虽然里面存在着很多不足,但项目都发挥着举足轻重的作用,要正确且全面的看待一个大型项目。
另一个开源项目OpenBLAS是BLAS和LAPACK API的开源实现,由张先轶博士领导开发,现已被广泛应用于很多基础软件中。在本课程中建议学生将自己实现的矩阵乘法与OpenBLAS的实现进行效率方面的对比,并鼓励学生设计尽可能逼近OpenBLAS性能的矩阵乘法。在对比中,OpenBLAS在性能上会以绝对优势领先学生的实现,这会激起学生的好奇心,进而去了解程序加速机制。学生通过一番努力如能达到OpenBLAS速度的1/4,则已经是非常优秀;也有极个别优秀学生达到OpenBLAS速度的70%。这些开源项目引导学生向更深处探索,可避免优秀学生“吃不饱”的问题。
声明:本文原发表于微信公众号“于仕琪”,可以转载,但不可修改。
3. 课程考核方式
好的考核手段,不仅可以有助于学生掌握基础知识,还可以激发学生对知识的兴趣。本课程的课程考核分有“小测验”、“练习题”、“期末考试”和“课程项目”四部分。
1. 小测验:小测验每周一次,理论课课程结束时在线上进行。小测验的内容为理论课课堂的主要知识点,通过大约10道有一定难度的客观题,测试学生对知识点的掌握。小测验必须在课程结束后30分钟内完成,逾期无分数。这种方式可以督促学生上课认真听讲。
2. 练习题:练习题在每周的实验课上完成,每次大约2-5道题,代码量大约50-100行。实验课上教师会简单讲一下练习题的要点,学生需要当堂完成练习,实验课上没有完成练习则无法得到分数。为了督促学生每周必须掌握本周知识点,不可拖延,所以练习题必须下课前完成,如完不成则没有分数,这可以避免学生将练习拖延到期末才做。
3. 期末考试:期末考试考核课程的关键知识点,如指针的应用,变量的初始化,类的各种特性等。期末考试的目的是让学生综合的复习和巩固课程中的重要知识点。
4. 课程项目:课程项目考核分数占比65%,是学生花精力最多的部分。课程项目一般有5个,大约3个星期1个项目。项目包括“简单计算器”,“C程序与Java程序在矩阵乘法效率的对比”,“高效率矩阵乘法实现”,“通用的矩阵类实现”,“卷积神经网络前向计算实现”等。考虑到本课程的重点是程序设计,而非软件工程,所以课程项目为单人项目,考核学生个人的综合开发能力。项目代码的评测不采用OJ自动判分系统,避免学生只考虑OJ系统考核的指标。项目的评分采用主观评分,由教师根据代码质量和项目报告质量综合考虑评分,引导学生以开发真正的工程项目为指导思想进行设计和开发。
声明:本文原发表于微信公众号“于仕琪”,可以转载,但不可修改。
4. 教学效果
4.1 课程受到学生欢迎
学生对课程的评教结果虽然不能直接评估课程的质量,但可以用来评估课程是否受学生欢迎。学生喜欢一门课,则会有比较高的评教分数。学生虽然不容易在本课程取得高分,但在过去两个学期分别获得94.00和94.43的评教得分。课程评教得分位列南科大计算机系课程的前1/3。学生在评语中普遍给出了非常正面的评价,同时也表达了他们对这门课程的喜爱。
4.2 学生参与开源项目开发进一步提升能力
学生在课程学习中,通过接触真正的项目代码,并通过课程项目的锻炼,有了一定的编程经验。一些学生学完课程后,不再畏惧真正的开发,参与了OpenCV项目开发。在过去3年中,共有22位同学为OpenCV贡献了条形码解码、二维码解码、三维点云降采样、三维点云压缩、三维点云平面检测、深度学习算子实现、深度模型量化、图像格式解码等10个算法和功能。
在OpenCV开发中,学生需要更加深入的理解OpenCV代码,熟悉代码编写规范。代码提交之后,开源社区的审核者会对代码提出很多修改意见,学生需要跟社区内的多位相关开发人员交流,并优化和改进自己的代码。通过参与真实的开发,学生提升了技术能力和交流能力,而且还提升了对真实项目的理解。
5. 未来挑战
随着ChatGPT等人工智能工具的出现,有越来越多的学生采用软件辅助来写项目代码和写项目报告。本课程规则中允许学生使用此类工具,但需要在报告中予以清晰的标注和说明。但在批阅中,教师难以准确评估使用软件的比例。例如代码大部分使用了软件生成,还是小部分?只是用软件辅助提供思路,还是以软件为主生成代码?软件辅助是提升了学生的能力,还是提升了作弊的能力?目前能准确评估学生的方式之一是教师和学生一对一面谈,教师对代码提问,学生对问题作答。但这种评估方式在学生人数多的情况下非常难以实施。随着人工智能技术的快速发展,传统的考核方式将会面临越来越多的挑战,相应的教学和考核方式应该随之改变。
致谢
本课程受“广东省在线开放课程”和“教育部-华为智能基座产教融合协同育人”项目资助。
声明:本文原发表于微信公众号“于仕琪”,可以转载,但不可修改。
参考文献
[1] 课程资料Github站点 https://github.com/ShiqiYu/CPP
[2] 课程授课视频 哔哩哔哩站点 https://www.bilibili.com/video/BV1Vf4y1P7pq
[3] "Adding support for the Rust language to the Linux kernel." https://github.com/Rust-for-Linux/linux
[4] Claburn, Thomas (2023-04-27). "Microsoft is rewriting core Windows libraries in Rust". https://www.theregister.com/2023/04/27/microsoft_windows_rust/ .
[5] 开源项目OpenCV https://www.opencv.org
[6] 开源项目OpenBLAS https://www.openblas.net/