云计算网络技术分享 - DPDK
type: channel
link: https://www.zhaozhanxu.com/tag/DPDK/
items: virtio-user简单性能测试
items: DPDK virtio-user
items: OVS-DPDK 添加端口流程
items: OVS-DPDK Bond模式了解
items: DPDK抓包方案
items: ovs-dpdk内存热扩容
items: DPDK的分片方案
items: [转]Cisco VPP BiHash分析
items: OVS-DPDK 瓶颈分析
items: [转]Cisco VPP Feature机制

virtio-user简单性能测试
type: item
date: 2017-12-28T14:33:00+08:00
description: 简单测试了一下virtio-user的性能。测试拓扑------- | | tap | host0 | host1 ------- | | --------------- ------------ | ------- --------| testpmd(dpdk) |----| eth0(dpdk) |-------|-------| eth0 | --------------- ------------ | -------通过iperf在host0的tap口和host1的eth0口进行收发报文。环境搭建我们只在host0上搭建virtio-user环境，host1依然采用原生的物理口DPDK版本为17.05.2，执行命令如下：testpmd -c 0x40004 -n 4 -d /lib64/librte_pmd_virtio.so --vdev=virtio_user0,path=/dev/vhost-net,queue_size=1024 -- -a --disable-hw-vlan --forward-mode=csum这是会启动一个tap0口，给tap0口配置IP。大包对比测试原生物理口测试为了找一个基准数据，我们在这两台服务器搭建virtio-user环境之前先用iperf进行流量测试，结果如下：TCP性能为 9.42 GbpsUDP性能为发 9.57 Gbps， 收 7.59 Gbps，有 20% 的丢包virtio-user作为发送方性能如下：TCP性能为 9.42 GbpsUDP性能为发 8.86 Gbps, 收 6.66 Gbps， 有 20% 的丢包virtio-user作为接收方性能如下：TCP性能为 9.42 GbpsUDP性能为发 6.33 Gbps，收 6.33 Gbps，有 0% 的丢包小包对比测试使用pktgen进行64Bytes的UDP报文发送，另一端通过sar -n DEV查看端口的kernel收包量，另外使用pktgen-dpdk查看一次端口的收包量，因为收包时过内核网络协议栈会严重降低收包量的这里我们不再使用一个tap口，而是通过virtio-user创建出多个tap口进行性能测试，并且只测试从tap口发送流量到另一端的物理端口。原生物理口测试发包性能为 578w pps， 内核收包 78w pps，dpdk收包 578w ppsvirtio-user生成2个tap口发包性能为 233w pps，内核收包 147w pps， dpdk收包 233w pps，这里感觉应该是两个tap口，两个IP给目的IP发送流量，两条流处理所以是上面单个物理口的两倍包量virtio-user生成4个tap口发包性能为 571w pps，内核收包 222w pps， dpdk收包 571w ppsvirtio-user生成8个tap口发包性能为 678w pps，内核收包 356w pps， dpdk收包 678w pps因为测试使用的是DPDK的例子testpmd，显示丢包都在tap口发送给testpmd的时候，testpmd转发并没有丢包，所以初步怀疑瓶颈是在tap处。

DPDK virtio-user
type: item
date: 2017-12-25T14:31:00+08:00
description: 主要查看一下virtio-user的工作思路，个人觉得他主要是用来替换KNI或者OVS的TAP设备，更好的用法应该是给container来用，主要是通过操作/dev/vhost-net创建kernel的tap设备用，然后kernel和virtio-user进行内存映射，便于收发报文。| 更正一下：目前来说container用virtio-user还不太合适，因为如果TAP设备加入到namespace的话，重启virtio-user就因为在当前的宿主机找不到TAP设备，会生成一个新的TAP设备。这块目前没有解决。官方图以上就是virtio-user的结构图，我们可以看到virtio-user驱动会在内核生成一个tap设备，然后另一端连接DPDK，报文通过内核发送到用户态。virtio-user创建tap端口流程接下来详细看一下，函数路径如下，就是初始化的时候，根据参数设置初始化虚拟设备。rte_eal_init-->rte_bus_probe-->vdev_probe-->vdev_probe_all_drivers-->virtio_user_pmd_probe主要是两个操作首先会解析一些参数，用来配置tap口，支持的参数为queues，cq，mac，path，queue_size，iface，这里面path是必备的，但是mac和iface我们可能也常用。如果进程是primary的话，调用virtio_user_dev_init初始化设备如果进程是secondary的话，调用rte_eth_dev_attach_secondary绑定设备，这里所做的就是简单的获取端口，默认就是由primary进行端口的配置了。eth_virtio_dev_init初始化vhost-user或者vhost-net设备virtio_user_dev_init-->virtio_user_dev_setup主要是根据支持对端为vhost-user和vhost-net，并且配置通知和中断，另外我们接下来主要关心一下vhost-net。virtio_user_dev_setup当是vhost-user时，得到vhost-user相关的ops，vhost-net时，得到kernel相关的vhost的ops，接下来就看kernel的vhost-net，不管vhost-user。首先申请vhostfds和tapfds的内存相关空间。调用vhost_kernel_setup打开相关的设备，比如我们打开的是dev/vhost-net。调用virtio_user_dev_init_notify去初始化通知。调用virtio_user_fill_intr_handle创建中断。eth_virtio_dev_initvirtio_init_device初始化设备rte_intr_callback_register注册中断回调virtio_init_devicevtpci_reset重置设备vtpci_set_status告诉host，注意到设备，并且知道怎么去驱动它了virtio_alloc_queues为设备创建队列virtio_configure_intr配置中断vtpci_reinit_complete实际调用vtpci_set_status告诉host，驱动完成。vtpci_set_status-->virtio_user_set_status如果驱动完成了，调用virtio_user_start_device驱动设备如果是重置标志，调用virtio_user_reset重置设备如果其他状态，则将状态记录在设备中virtio_user_start_devicevirtio_user_queue_setup调用virtio_user_create_queue，最终调用ioctl告知kernel的vhost，创建队列。dev->ops->send_request调用vhost_kernel_ioctl配置共享内存virtio_user_queue_setup调用virtio_user_kick_queue配置队列的vringdev->ops->enable_qp调用vhost_kernel_enable_queue_pair创建并且配置tap设备vhost_kernel_enable_queue_pair vhost_kernel_open_tap创建tap设备，并且进行配置。vhost_kernel_set_backend配置tap设备数据传递流程Kernel部分接下来我们看一下数据的传递路径，主要看一个流向，就是pktgen通过tap口进行流量发送，然后最终会传递给virtio-user的接收。pktgen发送直接调用的是tap的ndo_start_xmit，…

OVS-DPDK 添加端口流程
type: item
date: 2017-12-19T10:00:00+08:00
description: 这次因为遇到想要添加其他类型的端口，所以查看一下添加端口的流程。首先查看命令ovs-vsctl的实现，这里我的理解主要是操作ovsdb的，需要通过jsonrpc和ovsdb-server进行通信。现在我们主要是添加端口的，其他的先不看。utilities/ovs-vsctl.cmain-->do_vsctl-->cmd_add_port(c->syntax->run in vsctl_commands)-->add_port函数用来添加端口。add_port如果有--may_exist参数存在，则查看是否已经有该名字的port，有的话退出，没有的话继续。调用check_conflicts查看port和interface是否存在名字的冲突，冲突报错退出。找到相应的桥，然后添加port和interface，因为都是ovsrec操作，我们就不看了。ovsdb-server我们就不看了，因为就是用来连接ovs-vsctl和ovs-vswitchd的中转站。ovs-vswitchd.cmain-->bridge_run-->bridge_reconfigure-->bridge_add_ports-->bridge_add_ports__-->iface_create-->iface_do_create-->netdev_open(rc->class->construct)会根据类型来调用相应的type的端口。所以我们发现，我们只要在这里添加相应类型的一个netdev_class，然后模仿其他的type的一堆接口实现相应的函数即可，ovs-vsctl命令以及ovsdb-server都对type类型不关心，只是负责配置和存储，所以完全可以不用去操作。写到这里我们就先不写了，等真正需要添加端口的时候，再看看netdev_class数据结构各个函数指针的意义和函数。

OVS-DPDK Bond模式了解
type: item
date: 2017-12-11T16:00:00+08:00
description: 本文只是针对bond相关的流程，每个函数中只关注bond相关的操作。bond口添加首先是ovs-vsctl add-bond命令添加bond口，这里是配置到ovsdb里面，ovsdb-server进行处理的，我们暂时忽略，我们主要关注的是ovs-vswitchd进程监测到添加了bond口之后的操作。调用路径如下，主要是bridge_run函数从ovsdb-server进程得知端口发生了变化，就会去调用bridge_reconfigure进行重新配置，看样子是配置所有的，我们只关注bond相关的。main-->bridge_run-->bridge_reconfigure先将DPDK的多个端口添加到桥中，在ovs中的概念就是多个interface，一个port。最终会调用到netdev_dpdk_construct.bridge_reconfigure-->bridge_add_ports-->bridge_add_ports__-->iface_create-->iface_do_create-->netdev_open(rc->class->construct)-->netdev_dpdk_construct接着是端口的配置了port_configure开局先获取该port有多少个interface，即多少slave。port_configure_lacp首先是获取LACP的配置，有则调用iface_configure_lacp配置每个interface。slave > 1 表示是bond口，调用port_configure_bond最后调用ofproto_bundle_register-->bundle_set对bond口进行操作来看一下bundle_set调用lacp_configure配置LACP调用bundle_add_port添加端口如果已经创建了bond口了，则调用bond_reconfigure重新配置bond口，没有则调用bond_create创建bond口，里面也包含了配置。调用bond_slave_register注册slave以上主要就是端口添加的时候的操作，大部分都是数据结构添加和配置保存到结构中，已被运行的时候根据配置运行，接下来我们看一下bond口运行。bond口运行主要是非主线程的操作。主线程bond口运行调用流程如下：main-->bridge_run-->bridge_run__-->ofproto_run(p->ofproto_class->run)-->runrun中有两个bond相关的操作bundle_run是bond口的基本操作如果需要rebalance的话，bond_rebalance会做该操作，这块暂时不看了，没看懂，主要是recirc有点吓datapath的混了，不知道是不是一个，主要就是根据统计的报文大小看情况要不要迁移。bundle_run如果是LACP协议的话，lacp_run主要是根据rx定时器更新slave信息，更新lacp的attache信息，tx定时器超时候，生成并且发送LACPDU报文，重置定时器。是bond口的话，bond_run主要是监测连接状态，以防端口down掉不能接收发送报文。如果有需要，bundle_send_learning_packets会发送学习报文，主要是自己的MAC和VLAN信息，当是SLB、主备或者是不带协商的LACP时需要发送。不写了Bond口的运行分析到这里就差不多，本来觉得应该还有接收发送报文时候的bond相关的接口，但是没有查看到相关的操作，猜测应该是该bond是针对流表的balance，当然是可以rebalance，也就是datapath流表还是有多个物理口，下流表的时候，流表balance给bond的几个口，然后收发报文就都是相当于没有bond口那样的操作了，最后如果有口down掉的时候，从数据结构中删除，然后流表部分做相应的调整(不知道是不是流量迁移)，rebalance的时候，也可以流量迁移。官方给出的bond后使用方式，一般都是normal流表，以后有时间可以从这块去看一下，normal流表的时候怎么去balance流表的。

DPDK抓包方案
type: item
date: 2017-10-29T14:29:00+08:00
description: 想要看一看DPDK的抓包是怎么做的，我有没有改进的空间。用法这里不讲，可以查看官方文档。通过了解，DPDK的抓包是采用的C/S架构，DPDK有一个librte_pdump的库是server端，用来接收client端的指令去配置抓包，类似于linux kenrel做的工作，而client端就是我们运行的抓包命令，类似于tcpdump命令。librte_pdump由基于DPDK的主程序去调用rte_pdump_init创建server端。int rte_pdump_init(const char *path) { ... /* 设置unix socket domain的server端路径 */ rte_pdump_set_socket_dir(path, RTE_PDUMP_SOCKET_SERVER); /* 创建unix socket domain的socket */ pdump_create_server_socket(); /* 创建线程持续接收client端的信息，以便于在抓包时挂载抓包函数 */ ret = pthread_create(&pdump_thread, NULL, pdump_thread_main, NULL); /* 配置线程名字为pdump-thread，便于调试 */ snprintf(thread_name, RTE_MAX_THREAD_NAME_LEN, "pdump-thread"); ret = rte_thread_setname(pdump_thread, thread_name); return 0; }这里面我们比较重视的是pdump_thread_main，所以看一下该函数。static __attribute__((noreturn)) void * pdump_thread_main(__rte_unused void *arg) { /* 无限循环，没有退出机制 */ for (;;) { /* 从客户端接收信息 */ recvfrom(pdump_socket_fd, &cli_req, sizeof(struct pdump_request), 0, (struct sockaddr *)&cli_addr, &cli_len); /* 根据接收的信息挂载抓包函数 */ set_pdump_rxtx_cbs(&cli_req); /* 给客户端返回一些信息 */ resp.ver = cli_req.ver; resp.res_op = cli_req.op; resp.err_value = ret; sendto(pdump_socket_fd, &resp, sizeof(struct pdump_response), 0, (struct sockaddr *)&cli_addr, cli_len); } }接下来我们关注的是挂载抓包函数，所以看一下函数et_pdump_rxtx_cbs。static int set_pdump_rxtx_cbs(struct pdump_request *p) { /* 通过设备名称找到端口信息 */ if (operation == ENABLE) { rte_eth_dev_get_port_by_name(p->data.en_v1.device, &port); queue = p->data.en_v1.queue; ring = p->data.en_v1.ring; mp = p->data.en_v1.mp; } else { rte_eth_dev_get_port_by_name(p->data.dis_v1.device, &port); queue = p->data.dis_v1.queue; ring = p->data.dis_v1.ring; mp = p->data.dis_v1.mp; } /* 判断是抓所有队列的报文还是只抓一些队列的 */ if (queue == RTE_PDUMP_ALL_QUEUES) { rte_eth_dev_info_get(port, &dev_info); nb_rx_q = dev_info.nb_rx_queues; nb_tx_q = dev_info.nb_tx_queues; } /* register RX callback */ if (flags & RTE_PDUMP_FLAG_RX) { end_q = (queue == RTE_PDUMP_ALL_QUEUES) ? nb_rx_q : queue + 1; pdump_regitser_rx_callbacks(end_q, port, queue, ring, mp, oper…

ovs-dpdk内存热扩容
type: item
date: 2017-10-28T15:00:00+08:00
description: 一直在纠结这块要不要写出来，毕竟自己没有看懂，后来还是鼓起梁静茹给的勇气，看多少写多少吧。操作方式首先先说我们操作内存热扩容的命令操作，采用的是qemu monitor的方式，执行如下两条命令：两条命令# 此处一定要加share=on，不然会出现qemu扩的内存和dpdk映射的内存不是同一块 # 导致内存浪费和网络通信的问题 object_add memory-backend-file,id=mem1,size=4G,mem-path=/mnt/huge,share=on device_add pc-dimm,id=dimm1,memdev=mem1从文件hmp-commands.hx中我们可以看一下这两条命令针对的函数调用： { .name = "object_add", .args_type = "object:O", .params = "[qom-type=]type,id=str[,prop=value][,...]", .help = "create QOM object", .mhandler.cmd = hmp_object_add, .command_completion = object_add_completion, }, STEXI @item object_add @findex object_add Create QOM object. ETEXI { .name = "device_add", .args_type = "device:O", .params = "driver[,prop=value][,...]", .help = "add device, like -device on the command line", .mhandler.cmd = hmp_device_add, .command_completion = device_add_completion, }, STEXI @item device_add @var{config} @findex device_add Add device. ETEXImonitor_init-->monitor_command_cb-->handle_hmp_command最后会调用到命令行执行的函数，可以看到第一条命令对应的函数hmp_object_add，第二条命令对应的函数hmp_device_add，接下来我们针对这两个函数进行分析。内存扩容void hmp_object_add(Monitor *mon, const QDict *qdict) { /* 解析object的参数 */ QemuOpts *opts = qemu_opts_from_qdict(qemu_find_opts("object"), qdict, &err); /* 创建一些函数指针，下个函数会用到 */ OptsVisitor *ov = opts_visitor_new(opts); /* 创建实例，并且申请配置的实例大小 */ Object *obj = user_creatable_add(qdict, opts_get_visitor(ov), &err); }Object *user_creatable_add(const QDict *qdict, Visitor *v, Error **errp) { char *type = NULL; char *id = NULL; Object *obj = NULL; Error *local_err = NULL, *end_err = NULL; /* clone一份 */ QDict *pdict = qdict_clone_shallow(qdict); /* 调用opts_start_struct创建参数存储 */ visit_start_struct(v, NULL, NULL, 0, &local_err); /* 删除参数，并且解析参数出来 */ qdict_del(pdict, "qom-type"); visit_type_str(v, "qom-type", &type, &local_err); /* 同上 */ qdict_del(pdict, "id"); visit_type_str(v, "id", &id, &local_err); /* 创建目标，并且根据需求做相应的操作，这里我们就是申请内存 */ obj = user_creatable_add_type(type, id, pdict, v, &local_err); return obj; }Object *user_creatable_add_type(const char *type, cons…

DPDK的分片方案
type: item
date: 2017-05-05T14:28:00+08:00
description: 最近OVS-DPDK做分片重组的时候，遇到一些问题，所以现在大致看了一下分片思路和代码，现在记录一下。DPDK版本为16.11.1分片试验因为涉及到封装，所以我的分片不是基于标准的MTU 1500，而是基于1422，我采用ICMP报文，长度5000。以下记录了一下打印信息。从打印的信息可以看到，分片并尽量不使用copy，所以导致于新的分片都都几个段组成，中间记录了一些偏移量。in_packet buf_addr 0x7f49901b8180, data_off 128, buf_len 2184, nb_segs 4, pkt_len 5042, data_len 1514 buf_addr 0x7f49908ac680, data_off 162, buf_len 2184, nb_segs 3, pkt_len 1480, data_len 1480 buf_addr 0x7f49908abb40, data_off 162, buf_len 2184, nb_segs 2, pkt_len 1480, data_len 1480 buf_addr 0x7f49908ab000, data_off 162, buf_len 2184, nb_segs 1, pkt_len 568, data_len 568 out_packet buf_addr 0x7f49908aa4c0, data_off 114, buf_len 2184, nb_segs 2, pkt_len 1456, data_len 34 buf_addr 0x7f49901b8180, data_off 162, buf_len 2184, nb_segs 1, pkt_len 1500, data_len 1422 out_packet buf_addr 0x7f49908a8e40, data_off 114, buf_len 2184, nb_segs 3, pkt_len 1456, data_len 34 buf_addr 0x7f49901b8180, data_off 1584, buf_len 2184, nb_segs 1, pkt_len 1500, data_len 58 buf_addr 0x7f49908ac680, data_off 162, buf_len 2184, nb_segs 1, pkt_len 1480, data_len 1364 out_packet buf_addr 0x7f49908a6c80, data_off 114, buf_len 2184, nb_segs 3, pkt_len 1456, data_len 34 buf_addr 0x7f49908ac680, data_off 1526, buf_len 2184, nb_segs 1, pkt_len 1480, data_len 116 buf_addr 0x7f49908abb40, data_off 162, buf_len 2184, nb_segs 1, pkt_len 1480, data_len 1306 out_packet buf_addr 0x7f49908a4ac0, data_off 114, buf_len 2184, nb_segs 3, pkt_len 776, data_len 34 buf_addr 0x7f49908abb40, data_off 1468, buf_len 2184, nb_segs 1, pkt_len 1480, data_len 174 buf_addr 0x7f49908ab000, data_off 162, buf_len 2184, nb_segs 1, pkt_len 568, data_len 568入报文，长度位5042(icmp 5000+8+20+14)第一个报文是偏移162开始L4数据，长度1480接下来的2个报文也都是偏移162开始，长度1480最后一个报文偏移162开始，长度568mtu 1442出报文4个，总长度5144第一个报文新建一个L2和L3的头，接着使用入报文1的L4开始的数据，长度位1422，总长度34+1422=1456第二个报文新建一个L2和L3的头，接着使用入报文1的偏移1584(1422+162)，长度58(报文长度1422+58=1480)，接着使用入报文2的偏移162，长度1364，所以总长度34+58+1364=1456第三个报文新建一个L2和L3的头，接着使用入报文2的偏移1526(1364+162)，长度116(报文长度1364+116=1480)，接着使用入报文3的偏移162，长度1306，所以总长度34+116+1306=1…

[转]Cisco VPP BiHash分析
type: item
date: 2017-02-14T12:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念VPP里的Bihash全名为Bounded-index extensible hash。它的最大特点是，在查找时是无锁并且线程安全的。修改操作之间会有互斥，但是修改操作时仍然可以进行查找操作。vpp里的Bihash优化成了两种，bihash_kv_8_8和bihash_kv_24_8,区别在于hash key是8字节还是24字 节。最大限度的利用SSE4.2指令集中的_mm_crc32_u64来进行hash计算。核心函数在bihash_template.c中。根据包含的头文件是bihash_8_8.h还是bihash_24_8.h，BV宏和BTV 宏将把名字做出对应扩展。例如:BV (clib_bihash_init)扩展为clib_bihash_init_8_8()或者clib_bihash_init_24_8()。BVT (clib_bihash)扩展为clib_bihash_8_8_t或者为clib_bihash_24_8_t。clib_bihash_bucket_thash桶typedef struct { union { struct { //这个桶中记录得kv对，在heap中的起始位置 u32 offset; u8 pad[3]; //这个桶中记录的kv对，一共占用了1 << log2_pages个page u8 log2_pages; }; u64 as_u64; }; } clib_bihash_bucket_t;page数据结构，中间包含了kv对。typedef struct BV (clib_bihash_value) { union { BVT (clib_bihash_kv) kvp[BIHASH_KVP_PER_PAGE]; struct BV (clib_bihash_value) * next_free; }; } BVT (clib_bihash_value);核心函数clib_bihash_init_8_8()和clib_bihash_init_24_8()初始化bihash，并分配一个独占的内存heap给它。void BV (clib_bihash_init)(BVT (clib_bihash) * h, char *name, u32 nbuckets, uword memory_size) { void *oldheap; //方便之后hash值映射到bucket，用&替代昂贵的%操作。 nbuckets = 1 << (max_log2 (nbuckets)); h->name = (u8 *) name; h->nbuckets = nbuckets; h->log2_nbuckets = max_log2 (nbuckets); h->mheap = mheap_alloc (0 /* use VM */ , memory_size); //常用操作，这样才能使用内存操作函数， //注意这里单核心上只能有一个线程独占此操作，不同核心可以并发 oldheap = clib_mem_set_heap (h->mheap); vec_validate_aligned (h->buckets, nbuckets - 1, CLIB_CACHE_LINE_BYTES); h->writer_lock = clib_mem_alloc_aligned (CLIB_CACHE_LINE_BYTES, CLIB_CACHE_LINE_BYTES); clib_mem_set_heap (oldheap); }clib_bihash_search_8_8()和clib_bihash_search_24_8()给定key，查找value。int BV (clib_bihash_search)(const BVT (clib_bihash) * h, BVT (clib_bihash_kv) * search_key, BVT (clib_bihash_kv) * valuep) { //hash计算，者利用了SSE4.2指令集特性。值得记住 hash = BV (clib_bihash_hash) (search_key); //hash值的低log2_nbuckets bit用来索引桶号 bucket_index = hash & (h->nbuckets - 1); b = &h->buckets[bucket_index]; if (b->offset == 0) return -1; hash >>= h->log2_nbuckets; //在heap中的offset字节开始，属于桶。 v = BV (clib_b…

OVS-DPDK 瓶颈分析
type: item
date: 2017-02-03T20:33:00+08:00
description: 注：本文是参照了一些其他文章，原文地址点击这里。前言推荐看原文，因为我是懵逼的。并且这也算是广告贴了。谈到优化应用程序的性能，开发人员不必是性能专家，但应该精通自己的应用程序。许多方面的因素都会对应用程序性能产生影响，需要从硬件平台，代码设计和利用microarchitecture特性对功能代码进行微调。应用程序开发人员了解应用程序如何利用硬件资源的基本要求是，对代码设计和实现有深入理解。这可以通过对硬件microarchitecture有更深入的了解，或者通过使用诸如VTune™ Amplifier的专用分析工具来实现。top-down方法一个比较好的性能调优方法是top-down的方式。 这种方法有三个阶段：顶部的系统调整，中间的应用程序调整和底层的microarchitecture调整。 系统调整涉及硬件和操作系统调整，而应用程序调整包括更好的设计，并行化和提高库的效率。 microarchitecture是最后一个阶段，包括仔细选择编译器标志，向量化和代码重构w.r.t内存/缓存优化以及对CPU陷阱的理解，如下图所示。Microarchitecture和Micro-operations上图描述了第四代Intel® Core™ microarchitecture，其中上绿色部分是前端，下蓝色部分是pipeline的后端。前端是指令(例如ADD，MUL)被取出和解码(转换为较小的操作，称为micro-operations[μ-Ops])的地方。后端是执行所需操作，实际计算的地方。pipeline的每个块的详细描述超出了本文的范围。下图描述了前端和后端处理器流水线的职责。micro-operations(也称为μOps)成功完成整个datapath，称为retired。后端可以在每个cycle达到4个μOps。μOps通过(pass through)处理器pipeline并retired;偶尔由于各种分支错误预测，推测性地提取的几个μOps就会在其间被取消。pipeline slot表示处理一个μOp所需的硬件资源，这意味着在每个时钟周期的CPU核上有四个pipeline slot可用。如前所述，每个cycle可以有四个μOps retired，这意味着每个指令在理论上应该耗费0.25个cycle。在给定的时间点，pipeline slot可以为空或用μOps填充。pipeline slot分为四类，如下图所示。有关pipeline slot分类的更多信息，请参见本文。VTune™ AmplifierVTune Amplifier通过利用作为CPU内核的部分PMU(performance monitoring units)来收集测量结果。专用监视计数器可以收集和公开关于硬件资源消耗的信息。在PMU的帮助下，可以测量和检索关于所处理的指令的效率和缓存使用的度量。熟悉各种可用的指标（例如retired instructions; clock ticks; L2, L3 cache statistics; branch mis-predicts等）非常重要。此外，可以测量非CPU的PMU度量，如从/向存储器控制器读/写的字节，以及由Intel® QPI(Intel® QuickPath Interconnect)传输的数据流量。下面是用于计算不同pipeline slot度量的简要描述和公式。如果后端准备好接受更多μOps，而前端每个周期传送小于4μOps，那么应用程序考虑是front-end bound。这可能是由于提取代码(cache/ITLB问题)或解码指令的延迟所致。front-end bound pipeline slot可以被分类为子类别，如下图所示。公式：IDQ_UOPS_NOT_DELIVERED.CORE / (4 * Clockticks)如果由于在pipeline的后端缺少所需的资源而没有传送μOps，则应用程序考虑是back-end bound。这可能是因为内部结构填充了等待数据的μOps。下图中进一步描绘了front-end bound子类别。公式：Formula: 1 - (Front-end Bound + Bad speculation + Retiring)由于从分支错误预测或机器清除中恢复，μOps从不retire或分配slots被浪费时，pipeline slot被归类为bad speculation。公式：(UOPS_ISSUED.ANY - UOPS_RETIRED.RETIRE_SLOTS + 4* INT_MISC.RECOVERY_CYCLES) / (4* Clockticks)如果成功传递的μOps最终retired，那pipeline slot被归类为retiring公式：UO…

[转]Cisco VPP Feature机制
type: item
date: 2017-01-29T13:00:00+08:00
description: 注：本文是转载，但不是100%的转载，可能稍微有些出入，原文地址点击这里基本概念早期的VPP本身的node框架比较固定，各个node之间逻辑连接已经固化。为此新版本增加了feature机制，每个feature是一个node，用户可以启用/停止某个或某些feature。用户也可以自己写插件，把自定义node（自己的业务逻辑）加入到指定位置。vnet_feature_arc_registration_t vpp将feature分成不同的组，每组feature称为一个arc。arc中的feature按照代码指定的顺序串接起来。arc结构将记录这组feature的起始node和结束node。系统初始化时完成每个feature的连接。VNET_FEATURE_ARC_INIT宏用来注册arc。在arc中指定的起始node中，必须调用vnet_feature_arc_start函数，才能正式进入feature机制业务流程，该函数会将下一跳强行指定为arc中的下一个feature。vnet_feature_registration_t一个feature等价于一个node，用户通过VNET_FEATURE_INIT宏定义自己的feature，指定需要加入哪个arc，以及在arc中的哪个相对位置。每个feature都可以通过外部命令行启用/停止。关键函数VNET_FEATURE_ARC_INIT和VNET_FEATURE_ARC_INIT宏将arc和feature保存到全局变量feature_main中。此时arc和feature还未被”加工”，只是基本的原料。Feature初始化clib_error_t *vnet_feature_init将把arc和feature初始化，并组织成最终的数据结构填充到vnet_feature_main_t feature_main中，代码比较简单。这里只对vnet_feature_main_t描述即可。typedef struct { // feature arc configuration list //VNET_FEATURE_ARC_INIT宏中注册的arc保存在这里，顺序不定，也没有意义。 vnet_feature_arc_registration_t *next_arc; /* * vnet_feature_init()中来初始化该成员， * 把arc按名字组织成hash表,为何要用双重指针？？？ * 博主认为是bug，后面明明是当hash表用么。又不是hash数组 */ uword **arc_index_by_name; /* * feature path configuration lists * VNET_FEATURE_ARC_INIT宏中注册的feature保存在这里，顺序不定，也没有意义。 */ vnet_feature_registration_t *next_feature; /* * vnet_feature_init()中来初始化该成员，把feature分配到不同arc中， * 并且根据before和after变量来决定feature顺序。 * 这里看作是一个数组，用arc的index做索引 */ vnet_feature_registration_t **next_feature_by_arc; /* * vnet_feature_init()中来初始化该成员，把feature按名字组织成hash表数组， * arc的index索引到对应hash表，node name来做hash表的key */ uword **next_feature_by_name; // feature config main objects vnet_feature_config_main_t *feature_config_mains; // Save partial order results for show command char ***feature_nodes; /* * bitmap of interfaces which have driver rx features configured * bitmap数组，arc的index索引对应bitmap表,决定该网卡是否启用了该arc中的feature */ uword **sw_if_index_has_features; // feature reference counts by interface i16 **feature_count_by_sw_if_index; /* * Feature arc index for device-input * 从收包开始就使用feature机制了，这个变量给收包驱动用。 * 追踪了下代码…