当前位置: 首页 > 编程日记 > 正文

提高C++性能的编程技术笔记:设计优化/可扩展性/系统体系结构相关+测试代码

1. 设计优化

我们可以粗略地将性能优化分为两种类型:编码优化和设计优化。编码优化定义为不需要完整理解要解决的问题或者应用程序的执行流程就能实施的优化。通过定义看出,编码优化用于局部代码,同时该过程不牵涉周围的代码。除了这些容易实现的优化之外,剩下的所有优化都可以归结为设计优化。这些优化是系统性的----它们依赖于其它组件甚至一些关联度很低的模块的代码。设计优化贯穿于所有代码

设计灵活性:软件库是通用的,但应用程序却不是。将关注的焦点集中于代码的细节将会产生更高效的代码。可以通过降低灵活性来增加效率。

缓存:缓存先前的结果有多种不同的方式。其中的一种方式是将其作为对象的成员数据。如果你发现自己的程序不断重复地使用一个对象来获得一个相关的结果,那么更好的做法是将这个结果作为此对象的数据成员保存起来,并在将来的计算中使用。

高效的数据结构:软件性能通常等同于算法和所使用的数据结构的效率,有时算法和数据结构的效率被认为是影响软件性能的最重要因素。不论事实如何,使用高效率的算法是实现软件高效率的一个必要条件,这是毋庸置疑的。

延迟计算:许多性能优化通过更高效的计算方法来获取速度。一些重大的性能优化不仅可以通过提高计算速度实现,还通过消除计算来获得。

无用计算:

无效代码:造成的损害包括增加了可执行程序的规模、增大了内存占用,以及产生了源代码冗余。充斥着失效代码和僵死代码的源代码难于理解、维护和扩展。

在软件性能和灵活性之间存在一种基本的平衡。对于在80%时间内执行的20%的软件,性能通常损失在灵活性上。

在代码细节中可以利用缓存优化代码,在这个程序设计中也能采用这种方法。通常可以通过将先前的计算结果保存起来避免大量的计算。

对于软件的高效性而言,使用高效的算法和数据结构是必要条件,但并非充分条件。

有些计算只有在特定执行条件下才需要。这些计算应该被推迟到确实需要它们的路径上来完成。如果过早地执行计算,那么其结果可能并没有被调用。

大型软件往往会变得错综复杂,杂乱不堪。混乱软件的一大特点就是执行失效代码:那些曾经用来实现某个目标,但现在已经不需要的代码。定期清理失效和僵死代码可以增强软件性能,同时对于软件也是一种维护。

2. 可扩展性

可扩展性面对的挑战是使应用程序的运行速度跟得上处理器能力的提升。

主流的单处理器架构由一个CPU和一个主存模块组成,其中程序数据和指令存储在主存中。因为CPU的速度至少比主存快一个数量级,所以我们在处理器(CPU)和主存之间插入了一块快速内存作为缓冲。这块内存也被称为缓存。缓存可以提供更快的速度来访问程序数据和指令。缓存分为两个物理模块,其中一个存储数据,另一个存储指令。为了获取指令本身,对于每条指令处理器至少需要访问一次内存。而绝大多数的指令都需要额外内存参照物来获取数据。每当处理器需要访问内存时,首先会在缓存里寻找,因为缓存的访问命中率高达90%,所以访问主存的次数很少。

应用程序是由一个进程或多个协作进程组成。而每个进程由一个或多个线程组成。在现代操作系统中,调度的实体就是线程。操作系统执行的是线程,而不是进程。线程都放在系统的运行队列(Run Queue)里等待调度。在队列前端的线程就是下一个即将被执行的线程。

现代单处理器计算机由抢占式多任务处理操作系统控制,而该操作系统造成了程序是并发执行的错觉。从用户的角度来看,多个程序在并发执行。但正如所说的,这是一个错觉。在硬件的层面上,任意时刻内都仅有一个线程在单CPU上运行,多任务是通过线程轮流使用CPU来完成的。

对称多处理器(Symmetric MultiProcessor, SMP)架构:特点:首先是多处理器(MP),这种系统包含多个同样的CPU;其次是对称的,所有的处理器对于系统来说都是一样的,都有相同的处理能力。例如,它们都有一样的访问内存中的任何位置和任意I/O设备的能力。最后,剩余的组成部分是唯一的。这种架构只有一个内存系统、一个操作系统拷贝和一个运行队列。SMP架构具有扩展性的潜力。

Amdahl定律:针对应用程序的顺序执行部分会限制其可扩展性的情况,Amdahl定律做了量化分析。顺序(单线程)计算是可扩展性的主要绊脚石。

多线程和同步:以下面的简单代码为例:x = x + 1; 假设两个线程执行上面的语句,我们期望x的值变为x+2,当然其它的结果都是不正确的。但问题是这个语句并不是原子执行,实际上它会被分解为以下汇编语句:

load x, r5 // 将x的值读入r5寄存器
add r5, 1 // r5寄存器加1
store r5, x //将r5寄存器的值存回x

如果两个线程几乎同时执行这段代码,它们的指令可能以某种方式交替执行,而这种方式会导致x+1成为x的新值,而不是x+2。这种不合适的交叉执行线程被称为竞争条件,为了解决竞争条件,就需要保证这些汇编代码块是原子执行的,我们称这些代码块为临界区。一旦有线程进入临界区,只有当进入的线程离开后其它的线程才能进入。在这种情况下,我们称这两个线程是同步的(或者说串行化的),同时称该临界区是互斥的。因为变量x可以被多个线程访问,所以我们称它为共享数据,临界区就是为解决对共享数据的访问而设置的。为了保证共享数据的访问安全(以及正确执行),我们使用一个锁----一个标记(比特位或者整型)与共享数据相关联。线程进入临界区之前首先要检查锁的状态,如果锁的状态是关闭的,则说明当前没有其它线程在临界区内执行,因此允许该线程进入临界区,同时将锁的状态变为打开,以此来表示已有线程进入到临界区内(检查和设定锁状态的操作由硬件来保证是原子的)。如果锁是打开的,该线程必须要等待当前在临界区中执行的线程改变锁的状态后才能进入临界区。当临界区由锁来保护时,我们称它的执行是顺序进行的,因为想进入临界区的线程必须得在队列里等候,并且每次仅进入一个。

将任务分解为多个子任务:为了发挥可扩展性的潜力,应用程序必须将计算任务分为多个可以并行执行的子任务。

缓存共享数据:一旦能越过临界区访问,就会看到可扩展性为应用程序所带来的好处。这个想法的最简答的应用就是缓存共享数据,这些数据在将来的访问中不需要锁来控制就能重用。

无共享:减少共享是件好事,但并没有完全消除竞争。有时,可以为每个线程提供仅供其使用的私有资源而非共享资源,来完全消除共享。

部分共享:当每个线程都需要资源的单个实例时,可以使实例成为线程私有对象来轻松地消除竞争。如果不能事先确定所需实例的数量,你就需要使用资源池,让所有线程共享。这样共享资源经常成为线程竞争的焦点,会严重地降低性能和可扩展性,导致线程大量时间都处于空转状态。部分共享资源池提供了一种解决这种激烈竞争的方法。

锁粒度:使用单个锁来保护多个不相关的共享资源,一般来说都不是一个好主意。这样做会扩大临界区的范围,并造成其它不相关的线程间冲突。此规则的唯一例外是在满足以下两个条件时:所有共享资源总是一起操作;任何共享资源的操作都不会消耗大量的CPU周期。

伪共享:缓存的原子单元以行为单位。一般来说一个缓存行可以存储大量字节,典型的缓存行有128字节。当从主内存加载4字节的整数时,并不是仅加载这4字节,而是把包含它的整个行立即加载到缓存。同样,当另外的缓存(在不同的处理器上运行)使这个整数无效时,整个缓存行都是无效的。因此,变量在物理内存中的布局对于SMP的可扩展性十分重要。缓存一致性风暴将会严重地降低性能和可扩展性。

惊群现象:当线程无法获取繁忙的锁时,将会使用下面两种方式之一处理:(1). 进入自旋,每次循环都尝试获取锁。这对于短时的锁有效,但在空自旋等待资源可用的同时,长时间的自旋锁会导致线程耗尽CPU资源。(2). 线程进入休眠并等待唤醒,当锁释放后,所有等待的线程将醒来。我们把这种类型的锁称为简单锁,用来区分自旋锁。

当很多的线程并发地竞争简单锁,那么除了获得锁的线程,其余线程都进入休眠状态。当锁最后变得可用时,将会出现两种情况:(1). 只有一个线程被唤醒,它一般是等待队列上优先级最高的线程。(2). 所有的线程都被唤醒,这会导致”惊群线程”。唤醒所有等待线程的问题在于只有一个线程是真正被唤醒的,其余线程仍旧要回去等待。鉴于线程上下文切换的耗费巨大,我们会遭到性能和可扩展性上的巨大损失。

读/写锁:另一种消除同步问题的方式是放宽只能有一个线程独占访问共享数据的要求。共享数据有时候需要顺序来访问,是因为共享数据也许只被访问它的其中一个线程修改过。所以我们可以只允许一个修改数据的线程(写者)访问共享数据。相反,只读取共享数据的线程(读者)可以同时访问共享数据。读/写锁允许多个读者线程访问共享数据而不用等待独占访问。线程对共享数据进行读访问必须满足以下条件之一:(1). 没有其它线程访问数据;(2). 所有访问数据的线程都是读者线程。如果写者线程已获取访问权,所有的读者线程都必须等待写者线程离开临街区。当且仅当没有其它线程获取对共享资源的访问权,才可以授予写者线程访问权限。如果所有线程都要修改一个共享资源,读/写锁将不会有任何的帮助。实际上,这种类型的锁会降低性能,因为它们的实现更为复杂,所以性能就低于普通锁。但如果你的共享数据在绝大多数时间里在执行读操作,而读/写锁将消除读者线程间的竞争,所以能提高扩展性。

SMP是多处理器架构。它通过一条总线连接多个对称的处理器和一个内存系统。总线是SMP架构可扩展性的薄弱环节。让每个处理器都有自己的大缓存可以有效地控制总线的竞争。

Amdahl定律给出了一个应用的可扩展性的上限,顺序化计算限制了扩展性。

实现可扩展性的技巧是减少或者消除顺序化的代码。以下是可以达到这个目标的一些步骤:

任务分解:将大的任务分为小任务,使线程并发地执行这些小任务。

代码移出:临界区应该只包含关键代码,不直接操作共享资源的代码不要放在临界区内。

利用缓存:有时,通过缓存之前访问过的数据,可以消除对临界区的访问。

无共享:如果需要少量、数目固定的资源实例,可以不使用公共资源池。你可以把这些资源实例设为线程私有,并最后回收。

部分共享:有两个一样的资源池可以减少一半的竞争。

锁粒度:不要用同样的锁来保护所有资源,除非这些资源是同时更新的。

伪共享:不要在类定义里把两个使用频度都很高的锁放太靠近。你肯定不希望它们共享同一个缓存并触发缓存一致性风暴。

惊群现象:仔细分析你的锁调用的特征。当锁被释放时,是所有的等待线程都被唤醒还是只唤醒一个线程?唤醒所有线程会威胁到应用的可扩展性。

系统和类库调用:考察这些调用的实现特征。它们有可能是隐藏了顺序化的代码。

读/写锁:以读为主的共享数据会从这种锁中获益,使用这种锁,可以消除读者线程之间的竞争。

3. 系统体系结构相关话题

存储器层级:所有对性能的讨论最终都会集中到存储器使用和存储器使用模式上。通常来说,算法复杂度最核心的方面是算法需要的存储器访问量和访问类型。通常来说,一般的计算机都至少有5个存储器层级,而一些主要的存储器层级有时还包含子层级。存储器层级从最快(访问时间最短)到最慢(访问时间最长)其排序为:寄存器、L1(第一级)芯片内缓存、L2(第二级)芯片外缓存、主存(半导体动态随机访问内存:DRAM、SDRAM、RAMBUS、SyncLink等等)、磁盘存储器。

很多人仅根据访问时间和处理器速度来研究内存访问。然而,这种观点忽视了延时和带宽的交互作用。访问时间是一个延时问题。总线宽度和脉冲时间是带宽问题。延迟并不会和带宽以同样的比例改善,或者说,我们可以用较快的速度移动数据块,但是,访问内存中的单个字节的速度提升很慢。

寄存器:存储器之王。在存储器层级上的所有实体中,寄存器延迟最短,带宽最高,开销最小寄存器可由机器代码直接寻址。存储器层次结构的所有其它级别都基于虚拟或物理地址划分,或基于操作系统管理的存储系统(文件系统)索引来划分。保留字register告诉编译器,不要把变量放在内存,或者更精确地说,不要给变量分配内存地址。这只是一种建议,如inline保留字一样,编译器可能不理会。一些编译器会完全忽略寄存器指令。是否将一个变量放到寄存器,一个重要依据是变量被加载和存储的次数。正确地使用寄存器存放变量可以使某些编译器产生的个别方法在性能上大幅提升

磁盘和内存结构:一般来说,程序代码会显示出很好的局域性,而对代码的存储进行管理似乎并没有必要。在一些例子里,程序代码的活动集尺寸过于庞大时,就应该根据执行情况来重新安排代码的存储位置,将相关的代码放到同一个或者多个文件中去。因为性能原因而进行代码重组是另外一个方面的考虑,这种重组可以反馈给编译器,使编译器提供更好的自动优化。

缓存效应:缓存不仅可以提供对访问过的数据更快的访问速度,同时可以为以前访问过的数据提供一小块预取内存区域。缓存每次提取几行数据,并对其进行管理。典型的缓存行包含32字节数据,这些数据按32位边界对齐。通常缓存只能管理完整的缓存行。

缓存抖动:在多处理系统中,最有趣的缓存效应是缓存一致性协议给缓存性能带来的影响。缓存一致性协议是由内存/缓存控制器所使用的一种机制,该控制器维护多个内存的一致性,否则它们会成为不相关的缓存。缓存一致性协议是基于内存所有者的概念,以及同位缓存作读操作的显示验证。这些基本相当于一种协议,该协议允许多个缓存共享一个数据的拷贝,但在一个时间点只有一个缓存有写操作的权限。缓存一致性协议要求缓存之间直接通信。这种大量通信会降低系统的性能。主要的原因是缓存之间的通信通路是系统的共享资源,这样的通信必须是顺序进行的。如果内存总线忙于处理其它缓存事务,缓存就必须等待其它忙完后才能通信。这种等待缓存更新访问被称为缓存抖动(cache thrash)。缓存抖动是典型的多处理器不能维持其它处理器关系而导致的问题。

避免跳转:最快的代码是直线型的代码:没有条件判断,没有循环,没有调用,没有返回。一般来说,程序的关键路径越像一条直线,执行速度就越快。请记住:短小而带有很多分支的代码要比长而没有分支的代码所用的执行时间长

使用简单计算代替小分支:分支是性能的敌人。而很多时候,分支是不可避免的,但有时分支可以用计算来代替。这可能是一个重要的性能提升策略。

线程化的影响:多进程和多线程是两个关系密切但又相区别的并发编程类型。多进程允许多个独立的大进程相互通信。线程是子进程,它们是大进程的片段,但是它们都可以独立的调度。凡是从应用的角度讨论多任务都应该包含阻塞这个主题。这里有两种常见的I/O请求:阻塞和非阻塞,或者称为同步和异步。同步I/O要等待I/O请求完成后才能切换上下文,然后继续处理,也就是说同步I/O请求首先要求阻塞,直到I/O请求完成才做下一步的处理。异步I/O是在满足请求之前可以尽可能多地处理I/O事务,并要把所取得的进展返回到进程上下文,无论处理完多少事务,都不进行阻塞处理。通常还有一种中间I/O请求,这种类型的请求在某些时间里进行同步操作,而其余时间是异步操作。虽然在同步和异步操作之间的中间层可以给任务提供更有趣的操作能力,但是从性能的角度来看,它本质上仍是一种同步操作。

线程只有在同步请求资源或数据不可用时,才会阻塞。请求线程必须等待系统满足请求,而要满足这个请求就要完成I/O操作。当一个线程在等待相应的I/O操作完成时,要把它从处理器中切换出去,让其它没有被阻塞的线程运行。在I/O操作完成之后,线程再次变成可调度状态,并最终切换到处理器里运行。被阻塞的高优先级的线程将在I/O请求处理完成后立即恢复执行。

常见的程序是同步的单线程,它在某一时刻只能做一件事。一旦请求无法立即得到满足,整个程序就会被阻塞并等待操作系统完成它的请求。相反的是,一些复杂的程序会使用多个逻辑并发的物理和逻辑处理流来编写。这使得程序可执行多个相对独立的片段,而不用管它们在某个时间点是否完成了I/O请求。

多线程是一种相当有用的机制,而且在合适的系统中能带来巨大的性能优势,但错误地使用线程概念会导致严重的性能问题。多线程要求要进行上下文切换,而这种切换会耗费上千个处理周期。多线程还要频繁地使用锁来保护共享内存区域。锁也会浪费大量的处理周期,却对程序的完成没有任何作用。

上下文切换:是指将一个进程(线程)从处理器移出并把另外一个进程移入处理器。这个过程中要保存进程和处理器的状态。保存进程的状态是为了维护进程执行点的精确记录。保存处理器的状态是为了在相关进程继续执行时,处理器能返回原状态。处理器状态是进程状态的一部分,但不是全部。

进程上下文是操作系统所定义的一种结构,因此与操作系统关系密切。操作系统对该结构进行管理,这个结构包含进程的相关信息,如分配的内存范围、页映射表、打开的文件指针、子进程、处理器状态变量(寄存器、程序计数器、可能还有旁路转换缓冲,即Translation-Lookaside Buffer, TLB),还有进程状态变量如优先级、所有者、运行时间和当前状态(可调度、已阻塞、运行中)。大多数进程状态保存在内存里,只会偶尔访问,但进程上下文的处理器状态部分一直是由处理器使用的,并且必须放在处理器里用于进程执行。每次进程换出时,与进程相关的处理器状态要从处理器移到内存里,每次进程换入时,处理器要把这部分数据加载进来。

上下文切换有三个主要的开销:处理器上下文迁移、缓存和TLB丢失,以及调度开销。上下文切换和缓存之间的交互对程序的性能影响是最有害的。缓存的结构类型对系统性能的特性有显著的影响。根据访问缓存时所用的地址类型可以将缓存分为两类。虚拟地址缓存从进程的虚拟地址空间访问。虚拟地址与进程无关。另一种缓存类型是物理地址缓存,本质上与有ID标记的虚拟缓存相同。不同点在于,它不依赖于标记里的ID地址对,而是在提交缓存之前强制系统将虚拟地址转换为物理地址。

内核交叉:异步轮询相对于同步线程而言是一个很好的替代方案。虽然异步不是一种简单的软件技术,但在一些系统里,它可以带来很多性能的优势。这个问题与内核交叉的代价联系密切,同时又与上下文切换相关。在程序显式地调用服务例程,而该例程需要以内核权限执行时,就会发生内核交叉。在发生内核交叉时下面两件事中的一件也会发生:胖系统调用和瘦系统调用;而具体发生哪一个则取决于系统所使用的机制和服务请求的类型。

线程化选择:有3种主要的划分线程的方法:单个、小型和大型。单线程简单的形式就是一个程序,只由一个线程控制。然而,在更为复杂形式中,单线程程序可以使用异步I/O来实现逻辑并发和独立操作。单线程异步模型依赖于程序内部结构来监视程序各个部分的状态,同时依赖轮询来测试这些部分的I/O有效性。使用异步I/O可以保证:第一,没有子任务会因为一些I/O请求不能立即满足而阻塞整个任务;第二,程序的控制元素可以决定什么时候交出处理器的控制权,还可以决定什么时候继续处理。这样的系统往往围绕一个中央控制循环来构造,并顺序执行每个子任务。虽然这是一个很复杂的方法,但是在一些环境里,这种方法可以提供比多线程环境更好的性能。但如果使用不当,会出现无止境的空轮询,导致任务没有进展。大型线程是一种把请求当做独立处理实体的机制。可以理解为这种线程划分方法把每个重要的对象方法都看作一个线程。当重要对象被创建时,为其分配自己的线程,该线程将伴随对象的整个生命周期,从创建到销毁。这种机制可能非常高效,但也会在上下文切换时出现问题。小型多线程是将单线程和大型多线程方法相结合的机制。小型线程倾向于把子任务的完成作为中心,而不是把完成整改独立请求作为中心。决定采用何种线程时,最重要的方面可能是将要支持程序运行的硬件。

要使用的存储器离处理器越远,访问所需的时间就越长。离处理器最近的是寄存器,虽然容量很少,但是速度很快。对寄存器的优化对程序的性能提升而言是极其有意的。

虚拟存储器并不是无偿的,不加选择地依赖系统管理的虚拟结构可能会影响性能,而且一般都是降低性能。

上下文切换的开销巨大,需避免上下文切换。

以下是测试代码(design_optimizations.cpp):

#include "design_optimizations.hpp"
#include <string.h>
#include <iostream>
#include <chrono>
#include <string>
#include <numeric>
#include <vector>namespace design_optimizations_ {// reference: 《提高C++性能的编程技术》:第十四章:设计优化
namespace {void stringSum1(const std::vector<std::string>& vs, std::string& result)
{int vectorSize = vs.size();int* stringSizes = new int[vectorSize];int totalInputLength = 0;for (int i = 0; i < vectorSize; ++i) {stringSizes[i] = vs[i].length();totalInputLength += stringSizes[i];}char* s = new char[totalInputLength+1];memset(s, 0, totalInputLength+1);int sp = 0;for (int i = 0; i < vectorSize; ++i) {memcpy(&s[sp], vs[i].c_str(), stringSizes[i]);sp += stringSizes[i];}delete[] stringSizes;result = s;delete[] s;
}} // namespaceint test_design_optimizations_1()
{std::chrono::high_resolution_clock::time_point time_start, time_end;const int count{1000000}, count2{100000};std::vector<std::string> vectorx;for (int i = 0; i < 100; ++i) {vectorx.emplace_back("abcd");}{ // test string add: std::accumulate: 此函数的性能弱点测试time_start = std::chrono::high_resolution_clock::now();for (int i = 0; i < count; ++i) {std::string empty;std::string result = std::accumulate(vectorx.cbegin(), vectorx.cend(), empty);//fprintf(stdout, "result: %s\n", result.c_str());}time_end = std::chrono::high_resolution_clock::now();	fprintf(stdout, "string add operation std::accumulate time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}{ // test string add: custom function: 为了提高性能而牺牲了通用性time_start = std::chrono::high_resolution_clock::now();for (int i = 0; i < count; ++i) {std::string result;stringSum1(vectorx, result);//fprintf(stdout, "result: %s\n", result.c_str());}time_end = std::chrono::high_resolution_clock::now();	fprintf(stdout, "string add operation custom function time spend: %f seconds\n",(std::chrono::duration_cast<std::chrono::duration<double>>(time_end-time_start)).count());
}return 0;
}} // namespace design_optimizations_

执行结果如下:

GitHub: https://github.com/fengbingchun/Messy_Test

相关文章:

ICLR 2020被爆半数审稿人无相关领域经验,同行评审制度在垮塌?

作者 | 若名出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;根据维基百科&#xff0c;同行评议&#xff08;peer review)&#xff0c;是指由一个或多个具有与作品生产者具有相似能力的人员&#xff08;同行&#xff09;对作品进行的评估活动。同行评审方法用于维持质…

Swift 中使用 SQLite——批量更新(事务处理)

本文是Swift 中使用 SQLite系列的收官之作&#xff0c;介绍一下在数据库中的批量更新。 事务 在准备做大规模数据操作前&#xff0c;首先开启一个事务&#xff0c;保存操作前的数据库的状态开始数据操作如果数据操作成功&#xff0c;提交事务&#xff0c;让数据库更新到数据操…

网络管理常用命令之二 - Ipconfig 命令详解(图文)

2、Ipconfig 命令...不带参数.../all 参数.../release 和 /realease6 参数.../Renew 和 /Renew6 参数.../flushdns 参数.../displaydns 参数2、Ipconfig 命令 ipconfig命令也是使用率非常高的一个命令&#xff0c;可用于显示系统的TCP/IP网络配置值&#xff0c;并刷新动态主…

Swift 中使用 SQLite——查询数据

本文主要介绍如何查询 SQLite 结果集&#xff0c;以及封装 SQLite 的操作方法。 准备测试代码 /// 从数据库中加载 person 数组 class func persons() -> [Person]? {// 1. 准备 SQLlet sql "SELECT id, name, age, height FROM T_Person;"// 2. 访问数据库// …

提高C++性能的编程技术笔记:总结

《提高C性能的编程技术》这本书是2011年出版的&#xff0c;书中有些内容的介绍可能已经过时&#xff0c;已不再适用于现在的C编程中&#xff0c;但大部分内容还是很有参考意义的。 这里是基于之前所有笔记的简单总结&#xff0c;笔记列表如下&#xff1a; 跟踪实例&#xff1…

13岁小孩都跟我抢Python了,完了!

以下来自一位程序员母亲和工作人员的对话。程序员妈妈&#xff1a;您好&#xff0c;可以帮我推荐一本适合我家小孩看的编程书籍吗&#xff1f;兔子&#xff1a;可以的呀&#xff0c;《Scratch从入门到精通》&#xff0c;这本书适合小孩学习&#xff0c;您可以先看一下哦~程序员…

Windows Mobile 6.0 SDK和中文模拟器下载

【转】 Windows Mobile 6.0 SDK和中文模拟器下载 Windows Mobile 6.5 模拟器2010年12月06日 星期一 07:48转载自 zhangyanle86终于编辑 zhangyanle86Windows Mobile 6.0 SDK和中文模拟器下载 SDK 6.0下载页面&#xff1a;http://www.microsoft.com/downloads/details.aspx?fam…

wxPython:Python首选的GUI库 | CSDN博文精选

作者 | 天元浪子来源 | CSDN博客文章目录概述窗口程序的基本框架事件和事件驱动菜单栏/工具栏/状态栏动态布局AUI布局DC绘图定时器和线程后记概述跨平台的GUI工具库&#xff0c;较为有名的当属GTK、Qt 和 wxWidgets 了。GTK是C实现的&#xff0c;由于C语言本身不支持OOP&#x…

Swift 中使用 SQLite——修改和删除数据

本文主要介绍在SQLite中修改数据、删除数据&#xff1a; 更新记录 /// 将当前对象信息更新到数据库 /// /// - returns: 是否成功 func updatePerson() -> Bool {guard let name name else {print("姓名不能为空")return false}if id < 0 {print("id 不…

用python3实现指定目录下文件sha256及文件大小统计

有时会统计某个目录下有哪些文件&#xff0c;每个文件的sha256及文件大小等相关信息&#xff0c;这里用python3写了个脚本用来实现此功能&#xff0c;此脚本可跨平台&#xff0c;同时支持windows和linux&#xff0c;脚本(get_dir_file_info.py)内容如下&#xff1a; import os…

Swift 中使用 SQLite——新增数据

本文重点介绍两个方面&#xff0c;1、新增数据&#xff0c;2、获取自动增长 ID。 建立 Person.swift 数据模型 /// 个人模型 class Person: NSObject {// MARK: - 模型属性/// 代号var id: Int64 0/// 姓名var name: String?/// 年龄var age 0/// 身高var height: Double …

投稿2877篇,EMNLP 2019公布4篇最佳论文

整理 | AI科技大本营&#xff08;ID:rgznai100&#xff09;近日&#xff0c;自然语言处理领域的顶级会议之一EMNLP 2019公布了年度最佳论文。EMNLP是由国际语言学会&#xff08;ACL&#xff09;下属的SIGDAT小组主办的自然语言处理领域的顶级国际会议&#xff0c;是自然语言算法…

对象检测工具包mmdetection简介、安装及测试代码

mmdetection是商汤和港中文大学联合开源的基于PyTorch的对象检测工具包&#xff0c;属于香港中文大学多媒体实验室open-mmlab项目的一部分。该工具包提供了已公开发表的多种流行的检测组件&#xff0c;通过这些组件的组合可以迅速搭建出各种检测框架。 mmdetection主要特性&am…

(转)eclipse 代码自动补全

转自&#xff1a;http://blog.csdn.net/yushuwai2010/article/details/11856129 一般默认情况下&#xff0c;Eclipse的代码提示功能是比MicrosoftVisualStudio的差很多的&#xff0c;主要是Eclipse本身有很多选项是默认关闭的&#xff0c;要开发者自己去手动配置。如果开发者不…

swift 多线程GCD和延时调用

GCD 是一种非常方便的使用多线程的方式。通过使用 GCD&#xff0c;我们可以在确保尽量简单的语法的前提下进行灵活的多线程编程。在 “复杂必死” 的多线程编程中&#xff0c;保持简单就是避免错误的金科玉律。好消息是在 Swift 中是可以无缝使用 GCD 的 API 的&#xff0c;而且…

目标检测算法Faster R-CNN简介

在博文https://blog.csdn.net/fengbingchun/article/details/87091740 中对Fast R-CNN进行了简单介绍&#xff0c;这里在Fast R-CNN的基础上简单介绍下Faster R-CNN。 目标检测领域从R-CNN开始&#xff0c;通过引入卷积神经网络取得了很多突破性的进展&#xff0c;但是始终未能…

ICCV 2019 | 加一个任务路由让数百个任务同时跑起来,怎么做到?

作者 | Gjorgji Strezoski, Nanne van Noord, Marcel Worring 译者 | 中国海洋大学李杰 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;摘要传统的多任务&#xff08;MTL&#xff09;学习方法依赖于架构调整和大型可训练参数集来联合优化多个任务。但是&#xff0c;…

DEV开发之控件NavBarControl

右键点击RunDesigner弹出如下界面鼠标先点击3或4,1&#xff0c;&#xff0c;然后点击1或2进行相应的新增或删除操作&#xff0c;3是分组&#xff0c;4是项目&#xff0c;4可以直接拖动到相应的分组3.属性caption:显示的名称4.NavBarControl 属性 PaintStyleName绘画风格&…

swift支持多线程操作数据库类库-CoreDataManager

类库方法 获取数据 executeFetchRequest(request:) 同步获取数据 var request: NSFetchRequest NSFetchRequest(entityName: "MonkeyEntity")var myMonkeys:NSArray? CoreDataManager.shared.executeFetchRequest(request)异步获取数据 executeFetchRequest(re…

目标检测(或分隔)算法Mask R-CNN简介

在博文https://blog.csdn.net/fengbingchun/article/details/87195597 中对Faster R-CNN进行了简单介绍&#xff0c;这里在Faster R-CNN的基础上简单介绍下Mask R-CNN。 Mask R-CNN是faster R-CNN的扩展形式&#xff0c;能够有效地检测图像中的目标&#xff0c;并且Mask R-CNN…

未来之城,管理者可能不是人......

大会官网 https://t.csdnimg.cn/KSTh2010 年&#xff0c;IBM 正式提出了“智慧地球”愿景。在 IBM 的设想中&#xff0c;智慧城市应该由六个核心系统组成&#xff1a;组织&#xff08;人&#xff09;、业务/政务、交通、通讯、水和能源。&#xff08;图源 | IBM 官网&#xff0…

UVa 10701 - Pre, in and post

题目&#xff1a;已知树的前根序&#xff0c;中根序遍历转化成后根序遍历。 分析&#xff1a;递归&#xff0c;DS。依据定义递归求解就可以。 前根序&#xff1a;根&#xff0c;左子树&#xff0c;右子树&#xff1b; 中根序&#xff1a;左子树&#xff0c;根&#xff0c;右子树…

图像集存储成MNIST数据集格式实现

有时会用到将一组图像存放成MNIST中那样的数据格式&#xff0c;以便于用于网络的训练和测试&#xff0c;如MNSIT中的测试集标签t10k-labels.idx1-ubyte和测试集图像t10k-images.idx3-ubyte&#xff0c;各包含了10000个样本&#xff0c;这里以此两个测试集为例详细说明下实现过程…

ios9定位服务的app进入后台三分钟收不到经纬度,应用被挂起问题及解决方案

原来定位服务是10分钟收不到定位信息就挂起定位&#xff0c;现在变为最短3分钟&#xff0c;估计都是为了省电吧。只要你开启应用的后台定位&#xff0c;并且10分钟有一次定位&#xff0c;那么苹果就不会关闭你的线程&#xff0c;现在变成3分钟。若你的应用开启了后台定位&#…

程序员必知的20个Python技巧

作者 | Duomly 译者 | 弯月&#xff0c;编辑 | 郭芮 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;Python是一门流行且应用广泛的通用编程语言&#xff0c;其应用包括数据科学、机器学习、科学计算等领域&#xff0c;以及后端Web开发、移动和桌面应用程序等方面。…

CSS float浮动的深入研究、详解及拓展(二)

为什么80%的码农都做不了架构师&#xff1f;>>> 接上回… 五、浮动的非本职工作 浮动的本职工作是让匿名inline boxes性质的文字环绕图片显示&#xff0c;而其他所有用浮动实现的效果都不是浮动应该做的事情&#xff0c;我称之为“非本职工作”。 或许我们并没有…

不需要显示地图 就获得用户当前经纬度 超简单的方法

1.遵循协议 CLLocationManagerDelegate,AMapSearchDelegate,AMapLocationManagerDelegate 2. API MAMapServices.sharedServices().apiKey APIKey AMapLocationServices.sharedServices().apiKey APIKey AMapSearchServices.sharedServices().apiKey APIKey AMapNaviService…

ELECTRA:超越BERT,2019年最佳NLP预训练模型

作者 | 李如来源 | NLPCAB&#xff08;ID:rgznai100&#xff09;【导读】BERT推出这一年来&#xff0c;除了XLNet&#xff0c;其他的改进都没带来太多惊喜&#xff0c;无非是越堆越大的模型和数据&#xff0c;以及动辄1024块TPU&#xff0c;让工程师们不知道如何落地。今天要介…

安装node和spm过程

2019独角兽企业重金招聘Python工程师标准>>> 安装nodejs 官网下载nodejs&#xff0c;我下的是v0.10.33版本&#xff0c;安装到d:\nodejs下。 1.新建目录d:\nodejs&#xff0c;在其中建立node_cache、node_global、node_modules三个目录。 2&#xff0c;将C:\Users…

经典网络LeNet-5介绍及代码测试(Caffe, MNIST, C++)

LeNet-5&#xff1a;包含7个层(layer)&#xff0c;如下图所示&#xff1a;输入层没有计算在内&#xff0c;输入图像大小为32*32*1&#xff0c;是针对灰度图进行训练和预测的。论文名字为” Gradient-Based Learning Applied to Document Recognition”&#xff0c;可以直接从ht…