RDMA驱动学习(二)- command queue

为了实现用户对网卡硬件的配置,查询,或者执行比如create_cq等命令,mellanox网卡提供了command queue + mailbox的机制,本节将以create_cq为例看下这个过程。
command queue(后续简称cmdq)是一个4K对齐的长度为4K的连续物理内存,如图1所示,cmdq中有多个entry,entry的数量和entry的size都是从initialization segment中读到的。
在这里插入图片描述

图 1

entry格式如图2,软件的每一个cmd,会对应cmdq中的一个entry,cmd的input会存到entry中,如果input小于16B,那么就直接存储到command_input_inline_data,如果大于16B,那么剩余的会存到图一中的mailbox里,mailbox是一个链表结构,每个mailbox可以容纳512B的数据,第一个mailbox的指针存储在entry的input mailbox pointer。

在这里插入图片描述

图 2


将输入存到entry和对应的mailbox之后,软件会写cmdq的doorbell,doorbell位于initialization segment,可以理解为一个vector,软件会将这次entry对应的index写入到这个vector对应的bit通知硬件执行。硬件执行cmdq中entry的顺序是不确定的,初始化entry时,entry的ownership会被设置为HW,当硬件执行完成后,ownership会被重新设置为SW,因此软件可以轮询ownership确定硬件是否完成。

然后看下驱动中是怎么做的。

初始化

mlx5_cmd_init主要工作就是创建command queue的buf,然后将这块内存的总线地址告诉网卡硬件。
通过dma_pool_create创建dma内存池,每个内存块大小为sizeof(mlx5_cmd_prot_block),一个内存块表示了一个mailbox,mlx5_cmd_prot_block类型用于表示mailbox。

int mlx5_cmd_init(struct mlx5_core_dev *dev)
{int size = sizeof(struct mlx5_cmd_prot_block);int align = roundup_pow_of_two(size);struct mlx5_cmd *cmd = &dev->cmd;...cmd->pool = dma_pool_create("mlx5_cmd", mlx5_core_dma_dev(dev), size, align, 0);err = alloc_cmd_page(dev, cmd);...
}

然后通过dma_zalloc_coherent分配大小为一页的一致性dma映射,这块内存就是command queue的buf,虚拟地址保存在cmd_alloc_buf,dma地址保存到dma,同时还需要保证这个地址是页对齐的。如果不对齐,还需要重新分配一次大小为两页的dma映射,从而保证页对齐。

static int alloc_cmd_page(struct mlx5_core_dev *dev, struct mlx5_cmd *cmd)
{cmd->cmd_alloc_buf = dma_zalloc_coherent(mlx5_core_dma_dev(dev), MLX5_ADAPTER_PAGE_SIZE, &cmd->alloc_dma, GFP_KERNEL);.../* make sure it is aligned to 4K */if (!((uintptr_t)cmd->cmd_alloc_buf & (MLX5_ADAPTER_PAGE_SIZE - 1))) {cmd->cmd_buf = cmd->cmd_alloc_buf;cmd->dma = cmd->alloc_dma;cmd->alloc_size = MLX5_ADAPTER_PAGE_SIZE;return 0;}    dma_free_coherent(mlx5_core_dma_dev(dev), MLX5_ADAPTER_PAGE_SIZE, cmd->cmd_alloc_buf,cmd->alloc_dma);cmd->cmd_alloc_buf = dma_zalloc_coherent(mlx5_core_dma_dev(dev),2 * MLX5_ADAPTER_PAGE_SIZE - 1, &cmd->alloc_dma, GFP_KERNEL);...cmd->cmd_buf = PTR_ALIGN(cmd->cmd_alloc_buf, MLX5_ADAPTER_PAGE_SIZE);cmd->dma = ALIGN(cmd->alloc_dma, MLX5_ADAPTER_PAGE_SIZE);cmd->alloc_size = 2 * MLX5_ADAPTER_PAGE_SIZE - 1;return 0;
}

然后从Initialization Segment(后续简称iseg)读出cmdq_addr_l_sz,然后解析出低位的log_sz和log_stride,log_sz以log形式表示cmdq一共有多少个entry,log_stride以log形式表示cmdq的一个entry大小。然后将cmdq的dma地址写到iseg,这样硬件就知道cmdq的地址了。最后设置mode为CMD_MODE_POLLING,创建一个单线程的workqueue。到这里初始化就完成了。

int mlx5_cmd_init(struct mlx5_core_dev *dev)
{...cmd_l = ioread32be(&dev->iseg->cmdq_addr_l_sz) & 0xff;cmd->log_sz = cmd_l >> 4 & 0xf;cmd->log_stride = cmd_l & 0xf;cmd->max_reg_cmds = (1 << cmd->log_sz) - 1;cmd->bitmask = (1UL << cmd->max_reg_cmds) - 1;cmd_h = (u32)((u64)(cmd->dma) >> 32);cmd_l = (u32)(cmd->dma);iowrite32be(cmd_h, &dev->iseg->cmdq_addr_h);iowrite32be(cmd_l, &dev->iseg->cmdq_addr_l_sz);/* Make sure firmware sees the complete address before we proceed */wmb();...cmd->mode = CMD_MODE_POLLING;cmd->allowed_opcode = CMD_ALLOWED_OPCODE_ALL;cmd->wq = create_singlethread_workqueue(cmd->wq_name)...
}

cmd的下发与执行

输入输出

接下来以create_cq为例看下如何下发一个cmd,create_cq的输入由结构体mlx5_ifc_create_cq_in_bits解释,这个结构体不表示实际内存,比如opcode[0x10],通过u8类型表示仅仅是为了编程的方便,实际只占了16bit。输出由结构体mlx5_ifc_create_cq_out_bits表示。

struct mlx5_ifc_create_cq_in_bits {u8         opcode[0x10];u8         uid[0x10];u8         reserved_at_20[0x10];u8         op_mod[0x10];u8         reserved_at_40[0x40];struct mlx5_ifc_cqc_bits cq_context;u8         reserved_at_280[0x60];u8         cq_umem_valid[0x1];u8         reserved_at_2e1[0x59f];u8         pas[][0x40];
};

首先看下如何存储输入输出,mlx5_cmd_msg 表示一个msg,用于管理cmdq的entry和对应的mailbox,其中first用于存储开始的16B,如果输入大于16B,将会存储到next对应的mailbox链表里。mlx5_cmd_mailbox表示一个mailbox,其中buf为mailbox对应的虚拟地址,dma为这块buf的dma地址。next为软件侧的链,是一个虚拟地址,软件通过next遍历mailbox链表,硬件遍历mailbox的链表是通过buf里的next,是一个dma地址。

struct mlx5_cmd_msg {struct list_head        list;struct cmd_msg_cache           *parent;u32             len;struct mlx5_cmd_first       first;struct mlx5_cmd_mailbox        *next;
};struct mlx5_cmd_first {__be32      data[4];
};struct mlx5_cmd_mailbox {void           *buf;dma_addr_t  dma;struct mlx5_cmd_mailbox *next;
};

然后开始看执行cmd的过程,入口函数为mlx5_cmd_do,实际直接执行cmd_exec,其中callback和context为NULL,force_poling为false。

static int cmd_exec(struct mlx5_core_dev *dev, void *in, int in_size, void *out,int out_size, mlx5_cmd_cbk_t callback, void *context,bool force_polling)
{struct mlx5_cmd_msg *inb;struct mlx5_cmd_msg *outb;opcode = MLX5_GET(mbox_in, in, opcode);pages_queue = is_manage_pages(in);gfp = callback ? GFP_ATOMIC : GFP_KERNEL;inb = alloc_msg(dev, in_size, gfp);...
}

分配msg

cmd_exec首先通过alloc_msg分配input msg,初始化的时候已经分配了若干个mlx5_cmd_msg作为cache。如果in的长度小于16,就是可以放到一个cmdq entry,那么直接分配,不复用cache,否则会先看cache中有没有满足需要大小的msg,如果有则使用cache中的msg,假设没有命中cache,会通过mlx5_alloc_cmd_msg进行分配。

static struct mlx5_cmd_msg *alloc_msg(struct mlx5_core_dev *dev, int in_size,gfp_t gfp)
{struct mlx5_cmd_msg *msg = ERR_PTR(-ENOMEM);struct mlx5_cmd *cmd = &dev->cmd;if (in_size <= 16)goto cache_miss;...
cache_miss:msg = mlx5_alloc_cmd_msg(dev, gfp, in_size, 0);return msg;
}

先分配msg,然后通过mlx5_calc_cmd_blocks计算需要几个mailbox entry,计算方法为cmdq entry可以存16B,然后剩下的看需要几个mailbox存储,一个mailbox可以存储512B。然后通过alloc_cmd_box分配mailbox,这里就是通过dma_pool_zalloc从cmd->pool中获取一个dma内存块,对于每一个新分配的mailbox,通过mailbox的next字段链接到msg的next链表,软件通过这里的next就可以遍历链表。然后开始初始化mailbox的buf,设置block_num,设置token为0,然后设置buf的next为下一个mailbox的dma地址,硬件通过buf里的next就可以遍历链表了。

static struct mlx5_cmd_msg *mlx5_alloc_cmd_msg(struct mlx5_core_dev *dev,gfp_t flags, int size,u8 token)
{struct mlx5_cmd_mailbox *tmp, *head = NULL;struct mlx5_cmd_prot_block *block;struct mlx5_cmd_msg *msg;int err; int n;int i;msg = kzalloc(sizeof(*msg), flags);if (!msg)return ERR_PTR(-ENOMEM);msg->len = size;n = mlx5_calc_cmd_blocks(msg);for (i = 0; i < n; i++) {tmp = alloc_cmd_box(dev, flags);if (IS_ERR(tmp)) {mlx5_core_warn(dev, "failed allocating block\n");err = PTR_ERR(tmp);goto err_alloc;}    block = tmp->buf;tmp->next = head;block->next = cpu_to_be64(tmp->next ? tmp->next->dma : 0);block->block_num = cpu_to_be32(n - i - 1);block->token = token;head = tmp; }    msg->next = head;return msg; err_alloc:while (head) {tmp = head->next;free_cmd_box(dev, head);head = tmp; }    kfree(msg);return ERR_PTR(err);
}
static int cmd_exec(struct mlx5_core_dev *dev, void *in, int in_size, void *out,int out_size, mlx5_cmd_cbk_t callback, void *context,bool force_polling)
{...token = alloc_token(&dev->cmd);err = mlx5_copy_to_msg(inb, in, in_size, token);outb = mlx5_alloc_cmd_msg(dev, gfp, out_size, token);
}

然后开始分配token,token就是一个自增的uint8,标识一次cmd,一次cmd的cmdq entry和对应的所有mailbox的token都需要是一致的,然后通过mlx5_copy_to_msg将数据从in中拷贝到msg inb中,就是将连续的输入分散拷贝到cmd entry和mailbox中。然后分配output msg,和分配input msg一样。

执行

mlx5_cmd_invoke通过cmd_alloc_ent分配一个mlx5_cmd_work_ent ent,用于记录上下文信息,比如输入输出等。初始化ent中的work_struct work,对应的执行函数为cmd_work_handler,然后通过queue_work将ent->work提交到cmd->wq中执行,这里的wq就是初始化中创建的。wait_func通过wait_for_completion等待cmd->wq完成ent->work的执行。

static int mlx5_cmd_invoke(struct mlx5_core_dev *dev, struct mlx5_cmd_msg *in,struct mlx5_cmd_msg *out, void *uout, int uout_size,mlx5_cmd_cbk_t callback,void *context, int page_queue, u8 *status,u8 token, bool force_polling)
{struct mlx5_cmd *cmd = &dev->cmd;struct mlx5_cmd_work_ent *ent;ent = cmd_alloc_ent(cmd, in, out, uout, uout_size,callback, context, page_queue);ent->token = token;ent->polling = force_polling;init_completion(&ent->handling);if (!callback)init_completion(&ent->done);INIT_DELAYED_WORK(&ent->cb_timeout_work, cb_timeout_handler);INIT_WORK(&ent->work, cmd_work_handler);if (page_queue) {cmd_work_handler(&ent->work);} else if (!queue_work(cmd->wq, &ent->work)) {mlx5_core_warn(dev, "failed to queue work\n");err = -ENOMEM;goto out_free;}if (callback)goto out; /* mlx5_cmd_comp_handler() will put(ent) */err = wait_func(dev, ent);...
}

然后看下work_queue是如何执行的,即cmd_work_handler。
首先执行,cmd->bitmask表示了当前cdmq的buff有哪些entry可用,cmd_alloc_index就是通过find_first_bit找到bitmask中第一个置为1的位ret,如果找到了那么将这位置清0,表示占用了,将ret记录到ent->idx。

static int cmd_alloc_index(struct mlx5_cmd *cmd)
{       unsigned long flags;int ret;                spin_lock_irqsave(&cmd->alloc_lock, flags);ret = find_first_bit(&cmd->bitmask, cmd->max_reg_cmds);if (ret < cmd->max_reg_cmds)clear_bit(ret, &cmd->bitmask);spin_unlock_irqrestore(&cmd->alloc_lock, flags);return ret < cmd->max_reg_cmds ? ret : -ENOMEM;
}           
static void cmd_work_handler(struct work_struct *work)
{struct mlx5_cmd_work_ent *ent = container_of(work, struct mlx5_cmd_work_ent, work);struct mlx5_cmd *cmd = ent->cmd;bool poll_cmd = ent->polling;struct mlx5_cmd_layout *lay;int alloc_ret;int cmd_mode;dev = container_of(cmd, struct mlx5_core_dev, cmd);cb_timeout = msecs_to_jiffies(mlx5_tout_ms(dev, CMD));complete(&ent->handling);sem = ent->page_queue ? &cmd->pages_sem : &cmd->sem;down(sem);if (!ent->page_queue) {alloc_ret = cmd_alloc_index(cmd);if (alloc_ret < 0) { ...up(sem);return;}    ent->idx = alloc_ret;} else {...}    cmd->ent_arr[ent->idx] = ent; lay = get_inst(cmd, ent->idx);...
}

然后执行get_inst获取cmdq的第idx个entry,即lay,前边有说log_stride是以log表示的cmdq entry大小,因此就是(idx << cmd->log_stride)。

static struct mlx5_cmd_layout *get_inst(struct mlx5_cmd *cmd, int idx)
{       return cmd->cmd_buf + (idx << cmd->log_stride);
}

然后开始设置lay,首先拷贝input msg的前16B到lay->in,如果input msg有next,表示输入长度超过了16B,因此将第一个mailbox的dma地址设置到lay->in_ptr,接着设置inlen,同理设置out和outlen,然后设置token,signature,设置完成后将ownership bit修改为HW。

static void cmd_work_handler(struct work_struct *work) {...ent->lay = lay;memset(lay, 0, sizeof(*lay));memcpy(lay->in, ent->in->first.data, sizeof(lay->in));ent->op = be32_to_cpu(lay->in[0]) >> 16;if (ent->in->next)lay->in_ptr = cpu_to_be64(ent->in->next->dma);lay->inlen = cpu_to_be32(ent->in->len);if (ent->out->next)lay->out_ptr = cpu_to_be64(ent->out->next->dma);lay->outlen = cpu_to_be32(ent->out->len);lay->type = MLX5_PCI_CMD_XPORT;lay->token = ent->token;lay->status_own = CMD_OWNER_HW;set_signature(ent, !cmd->checksum_disabled);...
}

对lay的设置完成之后,然后开始写cmd queue的doorbell,doorbell是一个vector,软件通过写对应的bit表示新的cmd是cmd_buf的哪个位置。

static void cmd_work_handler(struct work_struct *work)
{...wmb();iowrite32be(1 << ent->idx, &dev->iseg->cmd_dbell);/* if not in polling don't use ent after this point */if (cmd_mode == CMD_MODE_POLLING || poll_cmd) {poll_timeout(ent);/* make sure we read the descriptor after ownership is SW */rmb();mlx5_cmd_comp_handler(dev, 1ULL << ent->idx, ent->ret == -ETIMEDOUT ?MLX5_CMD_COMP_TYPE_FORCED : MLX5_CMD_COMP_TYPE_POLLING);}
}

然后通过poll_timeout等待网卡执行,实现上就是通过轮询cmq entry的ownership字段,当ownership变成SW之后表示硬件已经完成执行。

static void poll_timeout(struct mlx5_cmd_work_ent *ent)
{struct mlx5_core_dev *dev = container_of(ent->cmd, struct mlx5_core_dev, cmd);u64 cmd_to_ms = mlx5_tout_ms(dev, CMD);unsigned long poll_end;u8 own;poll_end = jiffies + msecs_to_jiffies(cmd_to_ms + 1000);do {own = READ_ONCE(ent->lay->status_own);if (!(own & CMD_OWNER_HW)) {ent->ret = 0;return;}cond_resched();} while (time_before(jiffies, poll_end));ent->ret = -ETIMEDOUT;
}

然后执行mlx5_cmd_comp_handler将硬件输出的前16B拷贝到output msg,后边的输出可以通过output找到output mailbox获取,通过complete通知执行的完成。

static void mlx5_cmd_comp_handler(struct mlx5_core_dev *dev, u64 vec, enum mlx5_comp_t comp_type)
{struct mlx5_cmd *cmd = &dev->cmd;struct mlx5_cmd_work_ent *ent;...memcpy(ent->out->first.data, ent->lay->out, sizeof(ent->lay->out));complete(&ent->done);...
}

最后回到cmd_exec会将output msg拷贝回out,对于create_cq来说就是mlx5_ifc_create_cq_in_bits。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/465236.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在docker里创建 bridge 网络联通不同容器

1.网络创建&#xff1a; docker network create --subnet192.168.1.0/24 --gateway192.168.1.1 uav_management 2.查看网络&#xff1a; docker network ls 3.给已经创建的容器分配ip: docker network connect --ip 192.168.1.10 uav_management 容器名/容器id 示例&#xf…

影响神经网络速度的因素- FLOPs、MAC、并行度以及计算平台

影响神经网络速度的四个主要因素分别是 FLOPs&#xff08;浮点操作数&#xff09;、MAC&#xff08;内存访问成本&#xff09;、并行度以及计算平台。这些因素共同作用&#xff0c;直接影响到神经网络的计算速度和资源需求。 1. FLOPs&#xff08;Floating Point Operations&a…

基于STM32单片机太阳能充电循迹避障小车

本设计基于STM32单片机太阳能充电循迹避障小车&#xff0c;以STM32单片机为微控制器核心&#xff0c;在太阳能板对车载电池充电情况下配合传感器能够实现循迹避障行驶的小车的设计过程。小车采用3.7V锂电池作为电源&#xff0c;太阳能板接入TP4056充电模块&#xff0c;使用MT36…

高效集成金蝶云星空销售出库单的解决方案

审核销售出库单(金蝶->金蝶)集成方案 在企业日常运营中&#xff0c;销售出库单的审核是一个关键环节。为了确保数据的准确性和及时性&#xff0c;我们采用了轻易云数据集成平台&#xff0c;将金蝶云星空系统中的销售出库单数据无缝集成到另一个金蝶云星空系统中。本次案例将…

SpringBoot中使用RESTful风格

文章目录 SpringBoot中使用RESTful风格一、引言二、SpringBoot与RESTful风格1、RESTful风格简介2、SpringBoot中的RESTful注解2.1、代码示例 三、SpringBoot核心配置四、总结 SpringBoot中使用RESTful风格 一、引言 在现代Web开发中&#xff0c;RESTful架构风格因其简洁性和易…

DAY21|二叉树Part08|LeetCode: 669. 修剪二叉搜索树、108.将有序数组转换为二叉搜索树、538.把二叉搜索树转换为累加树

目录 LeetCode: 669. 修剪二叉搜索树 基本思路 C代码 LeetCode: 108.将有序数组转换为二叉搜索树 基本思路 C代码 LeetCode: 538.把二叉搜索树转换为累加树 基本思路 C代码 LeetCode: 669. 修剪二叉搜索树 力扣代码链接 文字讲解&#xff1a;LeetCode: 669. 修剪二叉搜…

ubuntu20.04安装ros与rosdep

目录 前置配置 配置apt清华源 配置ros软件源 添加ros安装源&#xff08;中科大软件源&#xff09; 设置秘钥 更新源 ros安装 安装ros 初始化 rosdep 更新 rosdep 设置环境变量 安装 rosinstall 安装验证 启动海龟仿真器 操控海龟仿真器 rosdep安装更新 安装 使用…

高亚科技签约酸动力,助力研发管理数字化升级

近日&#xff0c;中国企业管理软件资深服务商高亚科技与广东酸动力生物科技有限公司&#xff08;以下简称“酸动力”&#xff09;正式签署合作协议。借助高亚科技的8Manage PM项目管理软件&#xff0c;酸动力将进一步优化项目过程跟踪与节点监控&#xff0c;提升研发成果的高效…

CSRF与SSRF

csrf(跨站请求伪造)的原理: csrf全称是跨站请求伪造(cross-site request forgery)&#xff0c;也被称为one-click attack 或者 session riding scrf攻击利用网站对于用户网页浏览器的信任&#xff0c;劫持用户当前已登录的web应用程序&#xff0c;去执行分用户本意的操作。 利…

享元模式-实现大颗粒度对象缓存机制

详解 享元模式是一种结构型设计模式&#xff0c;其主要目的是通过共享尽可能多的相同部分来有效地支持大量细粒度的对象。它通过将对象的属性分为内在属性&#xff08;可以共享、不随环境变化的部分&#xff09;和外在属性&#xff08;根据场景变化、不能共享的部分&#xff0…

HTML 基础标签——结构化标签<html>、<head>、<body>

文章目录 1. <html> 标签2. <head> 标签3. <body> 标签4. <div> 标签5. <span> 标签小结 在 HTML 文档中&#xff0c;使用特定的结构标签可以有效地组织和管理网页内容。这些标签不仅有助于浏览器正确解析和渲染页面&#xff0c;还能提高网页的可…

新华三H3CNE网络工程师认证—VLAN的配置

VLAN&#xff08;虚拟局域网&#xff09;是一种在逻辑上划分网络的技术&#xff0c;它可以将一个物理网络分割成多个虚拟网络&#xff0c;从而实现不同组的设备之间的隔离。在配置VLAN时&#xff0c;通常涉及到三种端口类型&#xff1a;Access、Trunk和Hybrid。Access端口用于连…

R语言*号标识显著性差异判断组间差异是否具有统计意义

前言 该R代码用于对Iris数据集进行多组比较分析&#xff0c;探讨不同鸢尾花品种在不同测量变量&#xff08;花萼和花瓣长度与宽度&#xff09;上的显著性差异。通过将数据转换为长格式&#xff0c;并利用ANOVA和Tukey检验&#xff0c;代码生成了不同品种间的显著性标记&#x…

手边酒店多商户版V2源码独立部署_博纳软云

新版采用laraveluniapp开发&#xff0c;为更多平台小程序开发提供坚实可靠的底层架构基础。后台UI全部重写&#xff0c;兼容手机端管理。 全新架构、会员卡、钟点房、商城、点餐、商户独立管理

Multi Agents协作机制设计及实践

01 多智能体协作机制的背景概述 在前述博客中&#xff0c;我们利用LangChain、AutoGen等开发框架构建了一个数据多智能体的平台&#xff0c;并使用了LangChain的Multi-Agents框架。然而&#xff0c;在实施过程中&#xff0c;我们发现现有的框架存在一些局限性&#xff0c;这些…

ReactPress—基于React的免费开源博客CMS内容管理系统

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎提出宝贵的建议&#xff0c;感谢Star。 ![ReactPress](https://i-blog.csdnimg.cn/direct/0720f155edaa4eadba796f4d96d394d7.png#pic_center ReactPress 是使用React开发的开源发布平台&…

如何在一个 Docker 容器中运行多个进程 ?

在容器化的世界里&#xff0c;Docker 彻底改变了开发人员构建、发布和运行应用程序的方式。Docker 容器封装了运行应用程序所需的所有依赖项&#xff0c;使其易于跨不同环境一致地部署。然而&#xff0c;在单个 Docker 容器中管理多个进程可能具有挑战性&#xff0c;这就是 Sup…

【JavaEE初阶 — 多线程】线程安全问题 & synchronized

目录 1. 什么是线程安全问题 (1) 观察线程不安全 (2) 线程安全的概念 2. 造成线程安全的原因 (1)线程调度的随机性 问题描述 解决方案 (2)修改共享数据&#xff06;原子性问题 问题描述 解决方案 3.synchronized 关键字 1. synchronized 的特性 (1) …

产品经理的重要性

一直觉得产品经理很重要&#xff0c;这几年写了好几篇和产品经理相关的思考。2020年写过对产品经理的一些思考的文章&#xff0c;2021年&#xff0c;写了一篇对如何分析项目的思考&#xff0c;2024年写了如何与PM探讨项目。 今天还想再写一篇&#xff0c;主要是最近很有感慨。…

Hunyuan-Large:推动AI技术进步的下一代语言模型

腾讯近期推出了基于Transformer架构的混合专家&#xff08;MoE&#xff09;模型——Hunyuan-Large&#xff08;Hunyuan-MoE-A52B&#xff09;。该模型目前是业界开源的最大MoE模型之一&#xff0c;拥有3890亿总参数和520亿激活参数&#xff0c;展示了极强的计算能力和资源优化优…