63 research outputs found

    Area-energy aware dataflow optimisation of visual tracking systems

    Get PDF
    This paper presents an orderly dataflow-optimisation approach suitable for area-energy aware computer vision applications on FPGAs. Vision systems are increasingly being deployed in power constrained scenarios, where the dataflow model of computation has become popular for describing complex algorithms. Dataflow model allows processing datapaths comprised of several independent and well defined computations. However, compilers are often unsuccessful in identifying domain-specific optimisation opportunities resulting in wasted resources and power consumption. We present a methodology for the optimisation of dataflow networks, according to patterns often found in computer vision systems, focusing on identifying optimisations which are not discovered automatically by an optimising compiler. Code transformation using profiling and refactoring provides opportunities to optimise the design, targeting FPGA implementations and focusing on area and power abatement. Our refactoring methodology, applying transformations to a complex algorithm for visual tracking resulted in significant reduction in power consumption and resource usage

    Profile-guided redundancy elimination

    Full text link
    Program optimisations analyse and transform the programs such that better performance results can be achieved. Classical optimisations mainly use the static properties of the programs to analyse program code and make sure that the optimisations work for every possible combination of the program and the input data. This approach is conservative in those cases when the programs show the same runtime behaviours for most of their execution time. On the other hand, profile-guided optimisations use runtime profiling information to discover the aforementioned common behaviours of the programs and explore more optimisation opportunities, which are missed in the classical, non-profile-guided optimisations. Redundancy elimination is one of the most powerful optimisations in compilers. In this thesis, a new partial redundancy elimination (PRE) algorithm and a partial dead code elimination algorithm (PDE) are proposed for a profile-guided redundancy elimination framework. During the design and implementation of the algorithms, we address three critical issues: optimality, feasibility and profitability. First, we prove that both our speculative PRE algorithm and our region-based PDE algorithm are optimal for given edge profiling information. The total number of dynamic occurrences of redundant expressions or dead codes cannot be further eliminated by any other code motion. Moreover, our speculative PRE algorithm is lifetime optimal, which means that the lifetimes of new introduced temporary variables are minimised. Second, we show that both algorithms are practical and can be efficiently implemented in production compilers. For SPEC CPU2000 benchmarks, the average compilation overhead for our PRE algorithm is 3%, and the average overhead for our PDE algorithm is less than 2%. Moreover, edge profiling rather than expensive path profiling is sufficient to guarantee the optimality of the algorithms. Finally, we demonstrate that the proposed profile-guided redundancy elimination techniques can provide speedups on real machines by conducting a thorough performance evaluation. To the best of our knowledge, this is the first performance evaluation of the profile-guided redundancy elimination techniques on real machines

    An FPGA implementation of an investigative many-core processor, Fynbos : in support of a Fortran autoparallelising software pipeline

    Get PDF
    Includes bibliographical references.In light of the power, memory, ILP, and utilisation walls facing the computing industry, this work examines the hypothetical many-core approach to finding greater compute performance and efficiency. In order to achieve greater efficiency in an environment in which Moore’s law continues but TDP has been capped, a means of deriving performance from dark and dim silicon is needed. The many-core hypothesis is one approach to exploiting these available transistors efficiently. As understood in this work, it involves trading in hardware control complexity for hundreds to thousands of parallel simple processing elements, and operating at a clock speed sufficiently low as to allow the efficiency gains of near threshold voltage operation. Performance is there- fore dependant on exploiting a new degree of fine-grained parallelism such as is currently only found in GPGPUs, but in a manner that is not as restrictive in application domain range. While removing the complex control hardware of traditional CPUs provides space for more arithmetic hardware, a basic level of control is still required. For a number of reasons this work chooses to replace this control largely with static scheduling. This pushes the burden of control primarily to the software and specifically the compiler, rather not to the programmer or to an application specific means of control simplification. An existing legacy tool chain capable of autoparallelising sequential Fortran code to the degree of parallelism necessary for many-core exists. This work implements a many-core architecture to match it. Prototyping the design on an FPGA, it is possible to examine the real world performance of the compiler-architecture system to a greater degree than simulation only would allow. Comparing theoretical peak performance and real performance in a case study application, the system is found to be more efficient than any other reviewed, but to also significantly under perform relative to current competing architectures. This failing is apportioned to taking the need for simple hardware too far, and an inability to implement static scheduling mitigating tactics due to lack of support for such in the compiler

    Compilation techniques for short-vector instructions

    Get PDF
    Thesis (Ph. D.)--Massachusetts Institute of Technology, Dept. of Electrical Engineering and Computer Science, 2006.Includes bibliographical references (p. 127-133).Multimedia extensions are nearly ubiquitous in today's general-purpose processors. These extensions consist primarily of a set of short-vector instructions that apply the same opcode to a vector of operands. This design introduces a data-parallel component to processors that exploit instruction-level parallelism, and presents an opportunity for increased performance. In fact, ignoring a processor's vector opcodes can leave a significant portion of the available resources unused. In order for software developers to find short-vector instructions generally useful, the compiler must target these extensions with complete transparency and consistent performance. This thesis develops compiler techniques to target short-vector instructions automatically and efficiently. One important aspect of compilation is the effective management of memory alignment. As with scalar loads and stores, vector references are typically more efficient when accessing aligned regions. In many cases, the compiler can glean no alignment information and must emit conservative code sequences. In response, I introduce a range of compiler techniques for detecting and enforcing aligned references. In my benchmark suite, the most practical method ensures alignment for roughly 75% of dynamic memory references.(cont.) This thesis also introduces selective vectorization, a technique for balancing computation across a processor's scalar and vector resources. Current approaches for targeting short-vector instructions directly adopt vectorizing technology first developed for supercomputers. Traditional vectorization, however, can lead to a performance degradation since it fails to account for a processor's scalar execution resources. I formulate selective vectorization in the context of software pipelining. My approach creates software pipelines with shorter initiation intervals, and therefore, higher performance. In contrast to conventional methods, selective vectorization operates on a low-level intermediate representation. This technique allows the algorithm to accurately measure the performance trade-offs of code selection alternatives. A key aspect of selective vectorization is its ability to manage communication of operands between vector and scalar instructions. Even when operand transfer is expensive, the technique is sufficiently sophisticated to achieve significant performance gains. I evaluate selective vectorization on a set of SPEC FP benchmarks. On a realistic VLIW processor model, the approach achieves whole-program speedups of up to 1.35x over existing approaches. For individual loops, it provides speedups of up to 1.75x.by Samuel Larsen.Ph.D

    Contributions to worst-case execution time reduction using compilation techniques

    Get PDF
    Tese (doutorado) - Universidade Federal de Santa Catarina, Centro Tecnológico, Programa de Pós-Graduação em Engenharia de Automação e Sistemas, Florianópolis, 2017Abstract: A wide range of systems are distinct from the general purpose computingsystems due to the need of satisfying rigorous timing requirements, oftenunder the constraint of available resources, they are generally called realtimesystems. The development of a predictable system is concerned withthe challenges of building systems whose time requirements can be guaranteeda priori. Although, these challenges become even greater when usingprocessors architectural features for performance increase, as cachesand pipelines, which introduce a high degree of uncertainty, making difficultto provide any kind of guarantee. Parallel to this, there are the toolsneeded to develop and execute an application, such as languages, compilers,runtime support, communication systems and scheduling, which mayfurther make difficult the assertion of guarantees. In these systems, theresults of computations must be generated at the right time and faults oftemporal nature can result in catastrophic consequences both in the economicsense as in human lives. These systems are present in countlessapplications, such as in industrial plants, aviation, and the complexity ofthem imposes serious restrictions on the hardware that can be used. Toprovide timing guarantees, we must know the worst-case execution timefor each tasks of the system. In a general purpose architecture aimed atthe average case, the execution time of a program or task can be so greatin the worst case that invalidates the design constraints, or even be impossibleto be calculated or estimated with a reasonable effort. In thisthesis, we integrate compilation with WCET calculation. A compiler canprovide relevant data to facilitate the process of WCET estimation. Toimprove this process, we also use an architecture whose purpose is toconciliate performance with determinism. Considering compilation andWCET integration we present the following contributions: (1) a differentway to perform loop unrolling on data-dependent loops using codepredication targeting WCET reduction, because existing techniques onlyconsider loops with fixed execution counts. (2) considering static branchpredication techniques, we show that a very small gain or even none canbe obtained with new optimization techniques targeted to worst-case executiontime reduction. To achieve this objective, we compare several techniquesagainst the perfect branch predictor. (3) the difference between theWCET of a task and its actual execution time is called gain time. Wepropose a technique that finds specific points of a program (called gainpoints), where there will be an amount of statically estimated gain time inthe case that path is taken by the execution.Uma grande gama de sistemas se distinguem dos sistemas de computaçãode propósito geral pela necessidade de satisfação de requisitos detemporização rigorosos. O desenvolvimento de um sistema previsível preocupasecom os desafios de construção de sistemas cujos requisitos temporais possamser garantidos a priori. Estes desafios tornam-se ainda maiores quandose utiliza recursos arquiteturais para aumento de performance, como cachese pipelines, os quais introduzem um alto grau de incertezas, tornando difícilo provimento de qualquer tipo de garantia. Paralelamente a isto, existem asferramentas necessárias ao desenvolvimento e execução da aplicação, comolinguagens, compiladores, runtime de execução, sistemas de comunicação eescalonamento, os quais podem dificultar ainda mais a asserção de garantias.Nestes sistemas, os resultados das computações devem estar corretosnão somente do ponto de vista lógico, mas também devem ser gerados nomomento correto. As falhas de natureza temporal nestes sistemas são, emalguns casos, consideradas críticas no que diz respeito às suas consequências.Nos sistemas tempo real críticos (hard real-time) o não atendimento de umrequisito temporal pode resultar em consequências catastróficas tanto no sentidoeconômico quanto em vidas humanas. Quando os requisitos temporaisnão são críticos (soft real-time) eles apenas descrevem o comportamento desejado.O não atendimento de tais requisitos reduz a utilidade da aplicaçãomas não a elimina completamente nem resulta em consequências catastróficas.Estes sistemas estão presentes em diversas aplicações, como em plantasindustriais, aviação e eletrônica automotiva, telecomunicações e sistemasespaciais. Em várias destas aplicações, a complexidade dos sistemas de softwareimpõe sérias restrições quanto ao hardware que poderá ser utilizado.Este deverá ter capacidade suficiente para sustentar a aplicação em questão,além de poder estar submetido a restrições não funcionais do projeto, comocusto e eficiência energética.Arquiteturas modernas e de propósito geral possuem como premissabásica aquela que diz que os programas devem executar o mais rápido possívelna maioria das vezes. Este tempo médio é geralmente chamado deACET - Average-case Execution Time. Entretanto, em alguns casos, o tempode uma execução de uma aplicação poderá ser grande em relação ao caso médio,mas ainda estará amortizado entre as diversas execuções do programa.Esta priorização de caso médio impõe certas problemáticas quanto à utilizaçãodeste tipo de arquitetura em sistemas de tempo real. Tais sistemas podemexigir garantias de tempo de execução difíceis de serem obtidas ou muitasvezes inviáveis. Estas garantias exigem o conhecimento do pior tempo deexecução de um programa ou tarefa em um determinado processador, o qualgeralmente é chamado de WCET - Worst-case Execution Time. Em uma arquiteturade propósito geral que vise o caso médio, o tempo de execução nopior caso de um programa ou tarefa pode ser tão grande que inviabilize asrestrições de projeto, ou mesmo ser impossível de ser estimado.Atualmente, existem vertentes acadêmicas que sugerem a utilização deprocessadores e arquiteturas voltadas para aplicações de tempo real. Taisarquiteturas adotam características de hardware que tornam as análises referentesà obtenção de WCET mais simples e rápidas.Uma característica importante é que o desempenho em arquiteturas específicas,como as voltadas para tempo real, pode estar intimamente relacionadoao compilador e as técnicas de compilação empregadas, como exploraçãoestática de paralelismo. Dada a possibilidade de ser obter o WCET deprogramas para uma arquitetura específica, pode-se utilizar estas informaçõesno processo de otimização incremental dos mesmos. Estas otimizações visama redução do WCET, visto que abordagens tradicionais de transformação decódigo feitas por compilador podem até mesmo aumentar o WCET de umprograma.ObjetivosO objetivo deste trabalho é contribuir com aspectos relacionados à compilaçãopara sistemas de tempo real, cujo objetivo primário seja a reduçãode WCET ou melhoria de aspectos relacionados à escalonabilidade. A tesea ser demonstrada é que o íntimo acoplamento de um compilador com umanalisador WCET pode beneficiar tanto a análise quanto a síntese de um programaexecutável ou sistema completo para uma arquitetura determinista. Autilização de uma arquitetura determinista representa uma característica importantedeste trabalho, bem como o desenvolvimento do respectivo analisadorWCET.Dentre os elementos relacionados ao compilador essenciais para a reduçãodo WCET, pode-se citar: Mecanismos para o cálculo de WCET de programas em processo decompilação. Isto implica acoplamento do compilador com o analisadordesenvolvido. Identificação de potenciais pontos a serem beneficiados por otimizações.Este processo envolve interpretação dos resultados do analisador. Descarte de alterações de códigos que aumentem o WCET. Novamente,decisões deverão ser tomadas com base em análises sucessivas.Além dos elementos relacionados, podemos destacar a eficiência doprocesso. O uso de uma arquitetura projetada para aplicações em tempo realpermite o uso de um analisador muito mais rápido e preciso, que visa trazereficiência ao processo. Embora a arquitetura se baseie em um ISA comercial,não existe compilador livre disponível para esta, então, a implementação deum gerador de código inteiramente funcional fez-se necessária como requisitopara realização do trabalho de tese.Entre os elementos considerados como foco desta tese, têm-se: Técnicas de loop unrolling: Laços são frequentemente bons candidatosalvopara otimizações de compilação para extrair o desempenho emprocessadores modernos. Algumas técnicas foram propostas na literaturapara alcançar a redução do WCET usando o loop unrolling, comoem (ZHAO et al., 2006) e (LOKUCIEJEWSKI; MARWEDEL, 2010).Nestes trabalhos, apenas os laços com contagens de execução fixas sãoconsiderados. Previsão estática de desvios: Previsores de desvio são utilizados paraaumentar o desempenho de programas em arquiteturas modernas. Previsoresestáticos podem depender do compilador para definir o comportamentode cada desvio condicional. Esse comportamento é entãoadotado pelo processador para toda a execução do programa. O uso daprevisão estática de desvio como mecanismo para redução do tempo deexecução de pior caso é uma alternativa conhecida e foi primeiramenteproposta por (BODIN; PUAUT, 2005) e (BURGUIERE et al., 2005). Identificação de tempo ganho em programas: Tempo ganho (ou gaintime) (AUDSLEY et al., 1994) (AVILA et al., 2003) (HU et al., 2002)(HU et al., 2003) é a diferença entre o WCET de uma tarefa e o tempode execução real. Uma abordagem comum é identificar o gain timeem tempo de execução comparando o tempo de execução real (medido)com o WCET calculado estaticamente. A identificação do tempo deganho precoce é útil para aumentar a utilização do sistema em tempo deexecução e para economizar energia do sistema, por exemplo.Alcançar a redução do pior tempo de computação em tarefas que compõemum sistema de tempo real é importante pois permite que recursos computacionaisnão sejam desperdiçados, impactando diretamente no custo. Outraimportância para tal redução é a aceitação de tarefas do tipo soft real-time,pois quanto menor o WCET das tarefas do tipo hard, mais tempo de processadorpode ser alocado para este tipo de tarefa.ContribuiçõesAs contribuições desta tese para o estado da arte são:1. A proposição de uma maneira diferente de executar o loop unrollingsobre laços cujas execuções são dependentes de dados usando a predicaçãode código visando redução de WCET, porque as técnicas existentesconsideram apenas laços com contagens de execução fixas. A técnicaproposta também foi combinada com abordagens de loop unrollingexistentes. Os resultados mostraram que esta combinação pode produziragressivas reduções de WCET quando comparadas com o códigooriginal.2. Em relação às técnicas de predição estática de desvios, são mostradosque somente ganhos pequenos ou mesmo nenhum ganho pode ser obtidocom novas técnicas de otimização direcionadas para a redução do tempode execução do pior caso. Para alcançar esse objetivo, foram comparadasvárias técnicas contra o previsor de desvio perfeito. Este previsorpermite estimar a redução máxima de WCET que pode ser obtida comabordagens estáticas. Além da técnica clássica da literatura, foi incluídana comparação uma nova técnica centrada em WCET que atua comouma abordagem de força bruta para aproximar os resultados do preditorperfeito. A comparação também inclui técnicas de compilação nãodiretamente orientadas para redução de WCET. Como resultado, sãomostradas que as técnicas consideradas nesta tese estão próximas do resultadoótimo obtido pelo previsor perfeito. Também é mostrado quea técnica proposta produz resultados ligeiramente melhores do que asdemais técnicas. Como contribuição secundária, é mostrado que as técnicasinconscientes de WCET também podem ser usadas em ambientesem tempo real porque apresentam bons resultados e baixa complexidade.As técnicas de previsão foram avaliadas usando um conjunto deexemplos dos benchmarks para WCET de Mälardalen.3. Um problema do WCET é que ele é relativo a um único caminho de execução,especificamente o caminho de execução do pior caso (WCEP).Quando uma aplicação em tempo real executa sobre um caminho diferentedo WCEP, seu tempo de execução será provavelmente menor doque o WCET. A diferença entre o WCET de uma tarefa e seu tempo deexecução real é chamado de tempo ganho. Neste trabalho, é propostauma técnica que encontra pontos específicos de um programa (chamadospontos de ganho), onde haverá uma quantidade de tempo ganhoestimado estaticamente no caso de esse caminho ser tomado pela execução.Como estudo de caso, é apresentado o tempo ganho obtido pelaaplicação estratégia proposta a um benchmark da série de benchmarkspara WCET de Mälardalen. Para o benchmark selecionado, foram identificadosvários pontos de ganho e alguns deles com uma quantidadesignificativa de tempo ganho detectado estaticamente.ConclusãoSistemas de tempo real estão presentes em diversos segmentos da indústria,desde sistemas aviônicos a eletrônica automotiva, passando por sistemasindustriais. No passado, tais sistemas eram bastante simples, considerandoa demanda por recursos computacionais e interdependência entretarefas. Porém hoje o cenário é outro: têm-se aplicações com altíssimo nívelde complexidade, por vezes geradas sem intervenção humana a partir de modelosformais. Cada tarefa componente destas aplicações possui seu próprioprazo e por vezes depende de resultados provenientes de outras tarefas (possivelmenteatravés de uma rede), levando a necessidade de estimativa tambémde prazos fim-a-fim.Levantado o cenário anterior, percebe-se que processadores simples,como microcontroladores, não são capazes de atender aplicações de temporeal como atendiam no passado. Neste caso, torna-se necessária a utilizaçãode processadores com maior capacidade computacional, com mecanismos deaumento desempenho, como pipelines, caches e execução especulativa. Oproblema com estes mecanismos é a dificuldade de cálculo do pior caso notempo de computação, devido a fatores como anomalias temporais. Entretanto,algumas vertentes da literatura sugerem o uso de arquiteturas voltadaspara tempo-real, ou seja, deterministas.Neste trabalho, foi objetivada a geração e otimização de código parauma arquitetura determinista mas com mecanismos de aumento de performance.O objetivo primário foi a redução de WCET de programas, bemcomo o levantamento de alguns parâmetros úteis no projeto de um sistemade tempo real. A redução de WCET importante para não sobre-dimensionarsistemas, não desperdiçando assim, recursos computacionais. A utilizaçãode uma arquitetura determinista aliada a redução de WCET induz a sistemasbem dimensionados em termos de recursos.Usando técnicas como loop unrolling usando predicação de código eprevisão estática de desvios, foi possível reduzir o pior caso no tempo decomputação de tarefas. A caracterização de tempo ganho, do ponto de vistapuramente estático, também pôde ser alcançada neste trabalho

    Parameterized Algorithms for Scalable Interprocedural Data-flow Analysis

    Full text link
    Data-flow analysis is a general technique used to compute information of interest at different points of a program and is considered to be a cornerstone of static analysis. In this thesis, we consider interprocedural data-flow analysis as formalized by the standard IFDS framework, which can express many widely-used static analyses such as reaching definitions, live variables, and null-pointer. We focus on the well-studied on-demand setting in which queries arrive one-by-one in a stream and each query should be answered as fast as possible. While the classical IFDS algorithm provides a polynomial-time solution to this problem, it is not scalable in practice. Specifically, it either requires a quadratic-time preprocessing phase or takes linear time per query, both of which are untenable for modern huge codebases with hundreds of thousands of lines. Previous works have already shown that parameterizing the problem by the treewidth of the program's control-flow graph is promising and can lead to significant gains in efficiency. Unfortunately, these results were only applicable to the limited special case of same-context queries. In this work, we obtain significant speedups for the general case of on-demand IFDS with queries that are not necessarily same-context. This is achieved by exploiting a new graph sparsity parameter, namely the treedepth of the program's call graph. Our approach is the first to exploit the sparsity of control-flow graphs and call graphs at the same time and parameterize by both treewidth and treedepth. We obtain an algorithm with a linear preprocessing phase that can answer each query in constant time with respect to the input size. Finally, we show experimental results demonstrating that our approach significantly outperforms the classical IFDS and its on-demand variant

    From software to hardware: making dynamic multi-core processors practical

    Get PDF
    Heterogeneous processors such as Arm’s big.LITTLE have become popular as they offer a choice between performance and energy efficiency. However, the core configurations are fixed at design time which offers a limited amount of adaptation. Dynamic Multi-core Processors (DMPs) bridge the gap between homogeneous and fully reconfigurable systems. They present a new way of improving single-threaded performance by running a thread on groups of cores (compositions) and with the ability of changing the processor topology on the fly, they can better adapt themselves to any task at hand. However, these potential performance improvements are made difficult due to two main challenges: the difficulty of determining a processor configuration that leads to the optimal performance and knowing how to tackle hardware bottlenecks that may impede the performance of composition. This thesis first demonstrates that ahead-of-time thread and core partitioning used to improve the performance of multi-threaded programs can be automated. This is done by analysing static code features to generate a machine-learning model that determines a processor configuration that leads to good performance for an application. The machine learning model is able to predict a configuration that is within 16% of the performance of the best configuration from the search space. This is followed by studying how dynamically adapting the size of a composition at runtime can be used to reduce energy consumption whilst maintaining the same speedup as the fastest static core composition. An analysis of how much energy can be saved by adapting the size of the composition at runtime is conducted, showing that dynamic reconfiguration can reduce energy consumption by 42% on average. A model is then built using linear regression which analyses the features of basic blocks being executed to determine if the current composition should be reconfigured; on average it reduces energy consumption by 37%. Finally the hardware mechanisms that drive core composition are explored. A new fetching mechanism for core composition is proposed, where cores fetch code in a round-robin fashion. The use of value prediction is also motivated, as large core compositions are more susceptible to data-dependencies. This new hardware setup shows massive potential. By exploring a perfect value predictor with perfect branch prediction and the new fetching scheme, the performance of a large core composition can be improved by a factor of up to 3x, and 1.88x on average. Furthermore, this thesis shows that state-of-the-art value prediction with a normal branch predictor still leads to good performance improvements, with an average of 1.33x to up to 2.7x speedup

    Simulation Software as a Service and Service-Oriented Simulation Experiment

    Get PDF
    Simulation software is being increasingly used in various domains for system analysis and/or behavior prediction. Traditionally, researchers and field experts need to have access to the computers that host the simulation software to do simulation experiments. With recent advances in cloud computing and Software as a Service (SaaS), a new paradigm is emerging where simulation software is used as services that are composed with others and dynamically influence each other for service-oriented simulation experiment on the Internet. The new service-oriented paradigm brings new research challenges in composing multiple simulation services in a meaningful and correct way for simulation experiments. To systematically support simulation software as a service (SimSaaS) and service-oriented simulation experiment, we propose a layered framework that includes five layers: an infrastructure layer, a simulation execution engine layer, a simulation service layer, a simulation experiment layer and finally a graphical user interface layer. Within this layered framework, we provide a specification for both simulation experiment and the involved individual simulation services. Such a formal specification is useful in order to support systematic compositions of simulation services as well as automatic deployment of composed services for carrying out simulation experiments. Built on this specification, we identify the issue of mismatch of time granularity and event granularity in composing simulation services at the pragmatic level, and develop four types of granularity handling agents to be associated with the couplings between services. The ultimate goal is to achieve standard and automated approaches for simulation service composition in the emerging service-oriented computing environment. Finally, to achieve more efficient service-oriented simulation, we develop a profile-based partitioning method that exploits a system’s dynamic behavior and uses it as a profile to guide the spatial partitioning for more efficient parallel simulation. We develop the work in this dissertation within the application context of wildfire spread simulation, and demonstrate the effectiveness of our work based on this application
    corecore