实时性——如何进行跨任务性能分析

李肖遥 2022-04-18 22:02

说在前面的话】


在前一篇文章《实时性迷思(5)——实战RTOS多任务性能分析》中,我们介绍了如何在多任务环境下利用 perf_counter “排除多任务穿插的影响”——精确测量某一任务中指定代码片消耗CPU周期数的方法。还没有阅读过这篇文章的小伙伴可以单击这里,今天的内容将在这一基础上继续深入。


在实际应用中,很多数据处理的过程(或者是算法)基本都是由多个步骤构成,假设我们把每个步骤都“简单粗暴”的看做(或者放入)一个函数中的话,为了方便今天的讨论,它们可以被简化为如下的形式:
step_1();step_2();...step_n();

如果这些步骤总是


我们说好不分离~要一直一直在一起~



那么测量起来就非常简单:


  • 在裸机中,我们可以使用 __cycleof__()

__cycleof__() {    step_1();    step_2();    ...    step_n();}

也可以简单的使用系统提供的API函数:

start_cycle_counter();do {    step_1();    step_2();    ...    step_n();} while(0);int32_t iCycleUsed = stop_cycle_counter();


  • 在RTOS环境下,我们可以使用上一篇文章介绍过的专用函数 start_task_cycle_counter()stop_task_cycle_counter()来获取周期数信息:

void example_task (void *argument) {    init_task_cycle_counter();    ...    start_task_cycle_counter();    do {        step_1();        step_2();        ...        step_n();    } while(0);    int64_t lCycleUsed = stop_task_cyclee_counter();    ...}

如果你还不清楚问题的全貌,不妨看下面几张图:


一开始,在裸机中所有的步骤都是在一起的:

(理论上中断一关)我们就可以轻松的测量多个步骤所使用的CPU周期数,此时测量是连续进行的。当我们进入多任务环境时,虽然多个步骤仍然集中在同一个任务里,但由于“任务调度的存在”,实际上的情况就变得复杂起来:

借助每个任务独立的 start_task_cycle_counter()stop_task_cycle_counter() 我们得以“无视”任务调度带来的影响——只专注于本任务内指定范围的代码块所消耗的CPU时间

可是,现实是残酷的,发誓要“一直一直在一起”的“好朋友”也难免要各奔东西,更何况是同一个数据处理中的不同步骤呢?——各种各样的原因都会促使多任务应用设计时将不同的步骤分散到不同的任务中,比如:

  • 不同的步骤拥有不同的实时性要求

  • 不同的步骤处于不同的模块中

  • 不同的步骤处于不同的安全域中

  • 考虑到未来扩展的需要,认为的需要将步骤拆散并放置到不同的任务中

  • 不同的步骤处于数据流的不同位置

  • ……


此时,我们又该如何简单的测量这些分散在不同任务中的步骤所消耗的总CPU周期数呢?


【“活干完了告诉我,我先睡会儿”】

在简单的多任务合作模式中,如下图所示的“主从合作模式”是最常见的异步工作模式:

这里:

  1. 从任务的目的是从主任务那里分担一部分的工作;

  2. 从任务会在任务启动后完成必要的准备工作后就开始等候来自于主任务的信号量,此时,从任务是处于挂起状态;

  3. 一般来说,从任务应该在主任务发送信号量之前就完成所有的准备工作——否则,主任务就有“触发了个寂寞”的风险;

  4. 当从任务接收到来自主任务的信号量后,将从挂起状态中唤醒,开始正常工作;

  5. 当从任务完成本职工作后,会向主任务发送一个信号量——告诉它“你交代的事情我已经做完啦,我先睡会儿,有事您说话”——然后等待下一次主任务的信号量,并由此进入挂起状态。

  6. 主任务一般会在对应的阶段向从任务发送信号量,以启动异步处理,这就好比是对从任务说:“来活儿了,快醒醒,我先睡会儿”,然后就进入挂起状态——等待来自从任务的完成信号。


如果觉得上述步骤比较抽象,不妨来看一个实际的例子。假设一个数据处理可以被拆分成三个步骤——为了简化讨论,分别由三个函数 step_1()step_2()step_3() 表示:

void step_1(void){    delay_ms(1);}
void step_2(void){ delay_ms(2);}
void step_3(void){ delay_ms(3);}

这里,每个步骤都是用由 perf_counter提供的 delay_ms() 函数来模拟一个任务负载(注意:该 delay_ms() 函数不会引发RTOS的任务调度)。


假设三个步骤需要在一个任务中以10ms为间隔周期性的进行执行(以CMSIS-RTOS2的API为范例)

osThreadId_t s_tidTaskA;osThreadId_t s_tidTaskB;
void task_a (void *argument) {    ... while (1) {        // 获取本次循环开始时的系统毫秒数 uint32_t wTick = osKernelGetTickCount(); step_1(); step_2(); step_3(); //!< 100Hz 的周期性任务        osDelayUntil(wTick + 10);  }}

根据上一篇文章的内容,我们可以很容易的测量出三个步骤的CPU占用率:

osThreadId_t s_tidTaskA;osThreadId_t s_tidTaskB;
void task_a (void *argument) { init_task_cycle_counter();    ...    __super_loop_monitor__(100) {        // 获取本次循环开始时的系统毫秒数 uint32_t wTick = osKernelGetTickCount(); step_1(); step_2(); step_3(); //!< 100Hz 的周期性任务        osDelayUntil(wTick + 10);  }}

由于三个步骤中负载所占用的时间分别为 1ms、2ms、3ms,因此在10ms的循环周期中,容易计算出这里的CPU占用率为60%,而perf_counter的测量结果也应征了这一结论(100次循环的平均结果):



假设处于某种原因,步骤2必须要放置到一个独立的从任务中执行,根据前面的描述,对应的代码为:

osThreadId_t s_tidTaskA;osThreadId_t s_tidTaskB;
void task_a (void *argument) { init_task_cycle_counter(); ... __super_loop_monitor__(100) { uint32_t wTick = osKernelGetTickCount(); step_1();         //! 向从任务发送信号量,催其起床 osThreadFlagsSet(s_tidTaskB, 0x0001);        //! 等待从任务完成,挂起当前任务 osThreadFlagsWait(0x0002, osFlagsWaitAll, osWaitForever); step_3(); //!< 100Hz 的周期性任务        osDelayUntil(wTick + 10);  }}
void task_b (void *argument) { init_task_cycle_counter();    ...     while(1) {        //! 等待来自主任务的信号量 osThreadFlagsWait(0x0001, osFlagsWaitAll, osWaitForever); //! 干活 step_2(); //! 向主任务发送完成信号 osThreadFlagsSet(s_tidTaskA, 0x0002); }}

这里有两点需要注意:

1、为了保证主任务不会“触发了个寂寞”,task_b()需要先于task_a()启动,(或者最保险的方式是将从任务的优先级设置的“大于等于”主任务。)比如:

int main (void) {
// System Initialization SystemCoreClockUpdate();
osKernelInitialize(); // Initialize CMSIS-RTOS   init_cycle_counter(true); s_tidTaskB = osThreadNew(task_b, NULL, NULL); s_tidTaskA = osThreadNew(task_a, NULL, NULL);   ... if (osKernelGetState() == osKernelReady) { osKernelStart(); // Start thread execution }
while(1);}

2、别忘在两个任务的一开始使用 init_task_cycle_counter() 初始化对应任务的 cycle counter。


然而,经过上述修改后,我们发现实际测量到的 CPU 占用率为 40%



显然,该值由主任务中 step_1() 1ms step_3() 3ms 构成,而从任务中 step_2() 所消耗的时间则没有比计算在内——这就是跨任务周期计数的问题所在


为了应对这一问题,perf_counter 专门引入了可以跨越多个任务进行计数的计数器类 task_cycle_info_t,配合对应的方法(API函数)使用:


  • 构造函数(初始化函数):init_task_cycle_info()
/*! \brief intialize a given task_cycle_info_t object and enable it */extern task_cycle_info_t *init_task_cycle_info(task_cycle_info_t *ptInfo);
  • 在“涉事”任务内使用的注册和反注册:register_task_cycle_agent() 和 unregister_task_cycle_agent()

/*! \brief register a global virtual cycle counter agent to the current task *!  *! \note the ptAgent it is better to be allocated as a static variable, global *!       variable or comes from heap or pool */externtask_cycle_info_agent_t *register_task_cycle_agent(    task_cycle_info_t *ptInfo,    task_cycle_info_agent_t *ptAgent);
/*! \brief remove a global virtual cycle counter agent from the current task */externtask_cycle_info_agent_t *unregister_task_cycle_agent(task_cycle_info_agent_t *ptAgent);
  • perf_counter 的头文件中还能找到其它更为精细控制的方法,比如“使能开关相关的函数”等等,这里就不再赘述。


task_cycle_info_t 类的使用也非常简单:

1、以静态(或者堆、池)分配的方式获得一个 task_cycle_info_t 类的实例。比如定义一个静态变量:
static task_cycel_info_t s_tMyCycleInfo;


2、在所有相关的任务启动前,对其进行初始化(完成构造):

int main (void) {
// System Initialization SystemCoreClockUpdate();
osKernelInitialize(); // Initialize CMSIS-RTOS init_cycle_counter(true); init_task_cycle_info(&s_tMyCycleInfo); s_tidTaskB = osThreadNew(task_b, NULL, NULL); s_tidTaskA = osThreadNew(task_a, NULL, NULL); if (osKernelGetState() == osKernelReady) { osKernelStart(); // Start thread execution }
while(1);}


3、在所有“涉事”任务中,调用函数 register_task_cycle_agent() 注册我们的计数器实例,比如:

void task_a (void *argument) {    int64_t lTimeElapsed;        init_task_cycle_counter();        task_cycle_info_agent_t tCycleInfoAgent;    register_task_cycle_agent(&s_tMyCycleInfo, &tCycleInfoAgent);    start_task_cycle_counter(&s_tMyCycleInfo);        ...}

void task_b (void *argument) { init_task_cycle_counter(); task_cycle_info_agent_t tCycleInfoAgent; register_task_cycle_agent(&s_tMyCycleInfo, &tCycleInfoAgent); while(1) { osThreadFlagsWait(0x0001, osFlagsWaitAll, osWaitForever); step_2(); osThreadFlagsSet(s_tidTaskA, 0x0002); }}

这里需要注意:

  • 前面的例子中,涉事的任务是 task_a() task_b() 因此,这两个任务函数在完成了 init_task_cycle_counter() 后,都要调用 register_task_cycle_agent() 函数来注册 s_tMyCycleInfo

  • 注册时,需要借助一个 task_cycle_info_agent_t 的链表容器,帮助我们将 task_cycle_info_t 的实例加入到 每个任务自己的计数器链表中。这实际上也告诉我们,一个任务可以同时挂载多个不同的 task_cycle_info_t 实例——换句话说:每个任务都能同时服务多个不同目的的跨任务计数器,是不是很强大?

task_cycle_info_agent_t tCycleInfoAgent;register_task_cycle_agent(&s_tMyCycleInfo, &tCycleInfoAgent);


4、在开始计数时,通过 start_task_cycle_counter() 来启动我们的计数器:

start_task_cycle_counter(&s_tMyCycleInfo);

同理,在需要获得计数结果的时候,调用 stop_task_cycle_counter() 来获取计数结果:

int64_t lCycleUsed = stop_task_cycle_counter(&s_tMyCycleInfo);

这里,细心的小伙伴多半会注意到:这两个函数之前使用的时候不是不需要传递参数么?为什么又可以传递 task_cycle_info_t 类型的指针作为参数呢?其实这里使用了一个此前介绍过的技巧,还不太了解的小伙伴,可以参考这篇文章《【为宏正名】99%人都不知道的"##"里用法》,这里就不再赘述:


extern void __start_task_cycle_counter(task_cycle_info_t *ptInfo);
extern int64_t __stop_task_cycle_counter(task_cycle_info_t *ptInfo);

#define start_task_cycle_counter(...) \ __start_task_cycle_counter((NULL,##__VA_ARGS__))
#define stop_task_cycle_counter(...) \ __stop_task_cycle_counter((NULL,##__VA_ARGS__))


对应前面的例子,一个完整的示例代码如下:

void step_1(void){    delay_ms(1);}
void step_2(void){ delay_ms(2);}
void step_3(void){ delay_ms(3);}

osThreadId_t s_tidTaskA;osThreadId_t s_tidTaskB;
task_cycle_info_t s_tMyCycleInfo;

void task_a (void *argument) { int64_t lTimeElapsed; init_task_cycle_counter(); task_cycle_info_agent_t tCycleInfoAgent; register_task_cycle_agent(&s_tMyCycleInfo, &tCycleInfoAgent); start_task_cycle_counter(&s_tMyCycleInfo);     __super_loop_monitor__(100,     { lTimeElapsed = __cpu_usage__.lTimeElapsed; int64_t lCycleUsed = stop_task_cycle_counter(&s_tMyCycleInfo);         printf("s_tMyCycleInfo CPU Usage %2.3f%%\r\n",                     (float)((double)lCycleUsed* 100.0 / (double)__cpu_usage__.lTimeElapsed)); start_task_cycle_counter(&s_tMyCycleInfo); }) { uint32_t wTick = osKernelGetTickCount(); step_1(); //! 向从任务发送信号量,催其起床 osThreadFlagsSet(s_tidTaskB, 0x0001); //! 等待从任务完成,挂起当前任务 osThreadFlagsWait(0x0002, osFlagsWaitAll, osWaitForever);         step_3(); osDelayUntil(wTick + 10); //!< 50Hz }}
void task_b (void *argument) { init_task_cycle_counter(); task_cycle_info_agent_t tCycleInfoAgent; register_task_cycle_agent(&s_tMyCycleInfo, &tCycleInfoAgent); while(1) { //! 等待来自主任务的信号量 osThreadFlagsWait(0x0001, osFlagsWaitAll, osWaitForever); //! 干活 step_2(); //! 向主任务发送完成信号 osThreadFlagsSet(s_tidTaskA, 0x0002); }}
int main (void) {
// System Initialization SystemCoreClockUpdate(); osKernelInitialize(); // Initialize CMSIS-RTOS init_cycle_counter(true); init_task_cycle_info(&s_tMyCycleInfo); s_tidTaskB = osThreadNew(task_b, NULL, NULL); s_tidTaskA = osThreadNew(task_a, NULL, NULL); if (osKernelGetState() == osKernelReady) { osKernelStart(); // Start thread execution } while(1);}

运行结果如下:


可以看到,三个步骤的任务负载(1+2+3=6ms)都被计算在内。



友情提示:如果你对 __super_loop_monitor__() 结构的用法感到迷惑,可以单击这里。

【流水线模式下的性能测量】

除了前面介绍的简单“主从”模式外,多任务环境下往往还存在另外一种类似流水线的多任务“接力”模式:



这种任务模式在“数据流图”上往往呈现“百川汇海”模式,有时候,我们需要沿着其中一条线索完成从源头到末端的性能分析,而它的“事件触发图”大体如下:


这种情况下,我们仍然可以使用 task_cycle_info_t 来测量整个工序的耗时(周期数),只不过需要注意的是:

  • 我们要在工序开始的地方调用 start_task_cycle_counter()来开始计数;在工序结束的地方调用 stop_task_cycle_counter() 来获取测量结果。

  • 由于 start_task_cycle_counter() 会清零计数器,因此要在源头处作必要的保护——防止在一次完整的测量结束前,过早的调用 start_task_cycle_counter()。利用RTOS所提供的互斥量,我们可以轻松的实现这一功能,这里就不再赘述。

  • 测量的结果可以通过 SystemCoreClock 中保存的CPU工作频率换算成物理时间(ms或者us):

int64_t lCycleUsed = stop_task_cycle_counter(&s_tMyCycleInfo);
printf("Pipeline used %d ms", lCycleUsed / (SystemCoreClock / 1000) );
  • 有时候,进行性能分析需要暂时性的(或者有条件的)关闭某一计数器,此时灵活使用 使能开关函数 对 task_cycle_info_t 对象进行操作就成为了关键。


【说在后面的话】


跨任务性能测量是 perf_counter 所提供的“拳头功能”,可以说目前在市面上针对Cortex-M的开源工具中,还鲜有类似的功能。虽然关注【裸机思维】公众号后,在后台发送关键字 "perf_counter" 就可以获得对应 CMSIS-Pack 的网盘链接和相关的教程,但作为一个Github上的开源项目,我还是希望喜欢该工具的小伙伴能给我一个宝贵的Star。复制下面的链接到浏览器,或者单击“阅读原文” 就可以找到该项目。

https://github.com/GorgonMeducer/perf_counter


谢谢啦。




原创不易,


如果你喜欢我的思维、觉得我的文章对你有所启发,

请务必 “点赞、收藏、转发” 三连,这对我很重要!谢谢!


欢迎订阅 裸机思维

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

点击下面图片,有星球具体介绍,新用户有新人优惠券,老用户半价优惠,期待大家一起学习一起进步。


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
评论
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 202浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 58浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 124浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 223浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 108浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 70浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 92浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 141浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
    GIRtina 2025-01-07 11:02 122浏览
  • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
    丙丁先生 2025-01-07 09:25 116浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 164浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 158浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 61浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦